From ee35e1866b20be93bf9aee0a18867f3de75ee619 Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Sun, 5 Feb 2017 10:43:39 -0500 Subject: [PATCH 01/11] Add responseType code with token refresh interval --- app/scripts/directives/oauth.js | 12 +++-- app/scripts/services/access-token.js | 78 ++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 15 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index a5d9690..dd2ef76 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -30,6 +30,7 @@ directives.directive('oauth', [ template: '@', // (optional) template to render (e.g views/templates/default.html) text: '@', // (optional) login text authorizePath: '@', // (optional) authorization url + tokenPath: '@', // (optional) token 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 @@ -58,10 +59,13 @@ directives.directive('oauth', [ 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 + AccessToken.set(scope).then(function () { // sets the access token object (if existing, from fragment or session) + }) + ["finally"](function () { + 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 + }); }); }; diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 7e34d9b..91899a1 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', '$timeout', 'IdToken', function(Storage, $rootScope, $location, $interval, $timeout, IdToken){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken){ var service = { token: null @@ -15,6 +15,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', 'id_token' ]; var expiresAtEvent = null; + var refreshTokenUri = null; /** * Returns the access token. @@ -28,15 +29,28 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', * - takes the token from the fragment URI * - takes the token from the sessionStorage */ - service.set = function(){ - this.setTokenFromString($location.hash()); + service.set = function(scope) { + refreshTokenUri = scope.site + scope.tokenPath; + if ($location.search().code) { + return this.setTokenFromCode($location.search(), scope); + } else { + 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; + + var deferred = $q.defer(); + + if (this.token) { + deferred.resolve(this.token); + } else { + deferred.reject(); + } + + return deferred.promise; }; /** @@ -55,6 +69,25 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', service.expired = function(){ return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); }; + + service.setTokenFromCode = function (search, scope) { + return $http({ + method: "POST", + url: scope.site + scope.tokenPath, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "authorization_code", code: search.code, redirect_uri: scope.redirectUri, client_id: scope.clientId} + }).then(function (result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + $location.url($location.path()); + }); + } /** * Get the access token from a string and save it @@ -66,7 +99,6 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', if(params){ 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); @@ -93,6 +125,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', var params = Storage.get('token'); if (params) { setToken(params); + $rootScope.$broadcast('oauth:login', params); } }; @@ -106,6 +139,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', service.token = service.token || {}; // init the token angular.extend(service.token, params); // set the access token params setTokenInSession(); // save the token into the session + setExpiresAt(); setExpiresAtEvent(); // event to fire when the token expires return service.token; @@ -172,15 +206,37 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', } cancelExpiresAtEvent(); var time = (new Date(service.token.expires_at))-(new Date()); - if(time && time > 0 && time <= 2147483647){ - expiresAtEvent = $interval(function(){ - $rootScope.$broadcast('oauth:expired', service.token); - }, time, 1); + if(time && time > 0 && time <= 2147483647) { + if (service.token.refresh_token) { + expiresAtEvent = $interval(function() { + $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} + }).then(function (result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + }, function () { + $rootScope.$broadcast('oauth:expired', service.token); + }); + }, time); + } else { + expiresAtEvent = $interval(function() { + $rootScope.$broadcast('oauth:expired', service.token); + }, time, 1); + } } }; var cancelExpiresAtEvent = function() { - if(expiresAtEvent) { + if(expiresAtEvent && !service.token.refresh_token) { $timeout.cancel(expiresAtEvent); expiresAtEvent = undefined; } From ecf185f761d3ce659d37587a505e3943dc451326 Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Sun, 5 Feb 2017 12:10:19 -0500 Subject: [PATCH 02/11] Add build file --- dist/oauth-ng.js | 92 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 785f8fa..acf23ee 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2016-05-25 */ +/* oauth-ng - v0.4.10 - 2017-02-05 */ 'use strict'; @@ -366,7 +366,7 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $location, $interval, $timeout, IdToken){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken){ var service = { token: null @@ -379,6 +379,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', 'id_token' ]; var expiresAtEvent = null; + var refreshTokenUri = null; /** * Returns the access token. @@ -392,15 +393,28 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', * - takes the token from the fragment URI * - takes the token from the sessionStorage */ - service.set = function(){ - this.setTokenFromString($location.hash()); + service.set = function(scope) { + refreshTokenUri = scope.site + scope.tokenPath; + if ($location.search().code) { + return this.setTokenFromCode($location.search(), scope); + } else { + 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; + + var deferred = $q.defer(); + + if (this.token) { + deferred.resolve(this.token); + } else { + deferred.reject(); + } + + return deferred.promise; }; /** @@ -419,6 +433,25 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', service.expired = function(){ return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); }; + + service.setTokenFromCode = function (search, scope) { + return $http({ + method: "POST", + url: scope.site + scope.tokenPath, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "authorization_code", code: search.code, redirect_uri: scope.redirectUri, client_id: scope.clientId} + }).then(function (result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + $location.url($location.path()); + }); + } /** * Get the access token from a string and save it @@ -430,7 +463,6 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', if(params){ 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); @@ -457,6 +489,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', var params = Storage.get('token'); if (params) { setToken(params); + $rootScope.$broadcast('oauth:login', params); } }; @@ -470,6 +503,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', service.token = service.token || {}; // init the token angular.extend(service.token, params); // set the access token params setTokenInSession(); // save the token into the session + setExpiresAt(); setExpiresAtEvent(); // event to fire when the token expires return service.token; @@ -536,15 +570,37 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$location', } cancelExpiresAtEvent(); var time = (new Date(service.token.expires_at))-(new Date()); - if(time && time > 0 && time <= 2147483647){ - expiresAtEvent = $interval(function(){ - $rootScope.$broadcast('oauth:expired', service.token); - }, time, 1); + if(time && time > 0 && time <= 2147483647) { + if (service.token.refresh_token) { + expiresAtEvent = $interval(function() { + $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} + }).then(function (result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + }, function () { + $rootScope.$broadcast('oauth:expired', service.token); + }); + }, time); + } else { + expiresAtEvent = $interval(function() { + $rootScope.$broadcast('oauth:expired', service.token); + }, time, 1); + } } }; var cancelExpiresAtEvent = function() { - if(expiresAtEvent) { + if(expiresAtEvent && !service.token.refresh_token) { $timeout.cancel(expiresAtEvent); expiresAtEvent = undefined; } @@ -851,6 +907,7 @@ 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 + tokenPath: '@', // (optional) token 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 @@ -879,10 +936,13 @@ directives.directive('oauth', [ 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 + AccessToken.set(scope).then(function () { // sets the access token object (if existing, from fragment or session) + }) + ["finally"](function () { + 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 + }); }); }; From c359fa81c78c8c64b50df90b3d251a39f77a0b7d Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Sun, 12 Feb 2017 16:22:01 -0500 Subject: [PATCH 03/11] Clean code, refresh token on startup if a refresh_token is present --- app/scripts/directives/oauth.js | 2 +- app/scripts/services/access-token.js | 68 +++++++++++++++----------- dist/oauth-ng.js | 72 ++++++++++++++++------------ 3 files changed, 83 insertions(+), 59 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index dd2ef76..3188255 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -24,7 +24,7 @@ directives.directive('oauth', [ 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 + responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow, 'code' for authorization code flow and 'password' for resource owner password 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/access-token.js b/app/scripts/services/access-token.js index 91899a1..7744c89 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -33,14 +33,11 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q refreshTokenUri = scope.site + scope.tokenPath; if ($location.search().code) { return this.setTokenFromCode($location.search(), scope); - } else { - this.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){ - setTokenFromSession(); - } var deferred = $q.defer(); @@ -50,7 +47,11 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q deferred.reject(); } - return deferred.promise; + if(null === service.token) { + return setTokenFromSession(); + } else { + return deferred.promise; + } }; /** @@ -76,10 +77,10 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q url: scope.site + scope.tokenPath, headers: {'Content-Type': 'application/x-www-form-urlencoded'}, transformRequest: function(obj) { - var str = []; - for(var p in obj) + var str = []; + for(var p in obj) str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); + return str.join("&"); }, data: {grant_type: "authorization_code", code: search.code, redirect_uri: scope.redirectUri, client_id: scope.clientId} }).then(function (result) { @@ -121,13 +122,40 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q /** * Set the access token from the sessionStorage. */ - var setTokenFromSession = function(){ + var setTokenFromSession = function() { var params = Storage.get('token'); if (params) { setToken(params); - $rootScope.$broadcast('oauth:login', params); + if (!params.refresh_token) { + var deferred = $q.defer(); + deferred.resolve(params); + $rootScope.$broadcast('oauth:login', token); + return deferred.promise; + } else { + return refreshToken(); + } } }; + + var refreshToken = function () { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} + }).then(function (result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + }, function () { + $rootScope.$broadcast('oauth:expired', service.token); + }); + }; /** * Set the access token. @@ -209,23 +237,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q if(time && time > 0 && time <= 2147483647) { if (service.token.refresh_token) { expiresAtEvent = $interval(function() { - $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} - }).then(function (result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); - }, function () { - $rootScope.$broadcast('oauth:expired', service.token); - }); + refreshToken(); }, time); } else { expiresAtEvent = $interval(function() { diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index acf23ee..13cf466 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2017-02-05 */ +/* oauth-ng - v0.4.10 - 2017-02-12 */ 'use strict'; @@ -397,14 +397,11 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q refreshTokenUri = scope.site + scope.tokenPath; if ($location.search().code) { return this.setTokenFromCode($location.search(), scope); - } else { - this.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){ - setTokenFromSession(); - } var deferred = $q.defer(); @@ -414,7 +411,11 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q deferred.reject(); } - return deferred.promise; + if(null === service.token) { + return setTokenFromSession(); + } else { + return deferred.promise; + } }; /** @@ -440,10 +441,10 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q url: scope.site + scope.tokenPath, headers: {'Content-Type': 'application/x-www-form-urlencoded'}, transformRequest: function(obj) { - var str = []; - for(var p in obj) + var str = []; + for(var p in obj) str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); + return str.join("&"); }, data: {grant_type: "authorization_code", code: search.code, redirect_uri: scope.redirectUri, client_id: scope.clientId} }).then(function (result) { @@ -485,13 +486,40 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q /** * Set the access token from the sessionStorage. */ - var setTokenFromSession = function(){ + var setTokenFromSession = function() { var params = Storage.get('token'); if (params) { setToken(params); - $rootScope.$broadcast('oauth:login', params); + if (!params.refresh_token) { + var deferred = $q.defer(); + deferred.resolve(params); + $rootScope.$broadcast('oauth:login', token); + return deferred.promise; + } else { + return refreshToken(); + } } }; + + var refreshToken = function () { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} + }).then(function (result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + }, function () { + $rootScope.$broadcast('oauth:expired', service.token); + }); + }; /** * Set the access token. @@ -573,23 +601,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q if(time && time > 0 && time <= 2147483647) { if (service.token.refresh_token) { expiresAtEvent = $interval(function() { - $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} - }).then(function (result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); - }, function () { - $rootScope.$broadcast('oauth:expired', service.token); - }); + refreshToken(); }, time); } else { expiresAtEvent = $interval(function() { @@ -901,7 +913,7 @@ directives.directive('oauth', [ 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 + responseType: '@', // (optional) response type, defaults to token (use 'token' for implicit flow, 'code' for authorization code flow and 'password' for resource owner password 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 71312a98cfe53eb2a6839a99a6c78f4196d024e3 Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Mon, 13 Feb 2017 07:55:54 -0500 Subject: [PATCH 04/11] response_type password in progress --- app/scripts/directives/oauth.js | 59 +++++++++++---- app/scripts/services/access-token.js | 44 ++++++++++- app/views/templates/button.html | 26 ++++++- app/views/templates/default.html | 26 ++++++- dist/oauth-ng.js | 105 +++++++++++++++++++++------ dist/views/templates/button.html | 26 ++++++- dist/views/templates/default.html | 26 ++++++- 7 files changed, 259 insertions(+), 53 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 3188255..e6af37d 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -32,13 +32,13 @@ directives.directive('oauth', [ authorizePath: '@', // (optional) authorization url tokenPath: '@', // (optional) token 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 - // 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 - wellKnown: '@', // (optional for OpenID Connect) whether to load public key according to .well-known/openid-configuration endpoint + storage: '@', // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' + 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 + 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. } @@ -70,14 +70,17 @@ directives.directive('oauth', [ }; 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'; + 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'; + scope.typedLogin = ""; + scope.typedPassword = ""; + scope.typedKeepConnection = false; }; var compile = function() { @@ -100,7 +103,7 @@ directives.directive('oauth', [ var initView = function () { var token = AccessToken.get(); - if (!token) { + if (!token && scope.responseType !== "password") { return scope.login(); } // without access token it's logged out, so we attempt to log in if (AccessToken.expired()) { @@ -123,6 +126,28 @@ directives.directive('oauth', [ $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; }; + + scope.checkPassword = function () { + $http({ + method: "POST", + url: scope.site + scope.tokenPath, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "password", username: scope.typedLogin, password: scope.typedPassword, scope: scope.scope} + }).then(function (result) { + AccessToken.set(result.data, scope.typedLogin, scope.typedPassword, scope.scope).then(function () { // sets the access token object (if existing, from fragment or session) + }); + $rootScope.$broadcast('oauth:login', result.data); + scope.show = "logged-in"; + }, function () { + $rootScope.$broadcast('oauth:denied'); + }); + }; scope.$on('oauth:expired',expired); @@ -169,6 +194,8 @@ directives.directive('oauth', [ scope.$on('$stateChangeSuccess', function () { $timeout(refreshDirective); }); + + }; return definition; diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 7744c89..1c3ec95 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -5,7 +5,10 @@ var accessTokenService = angular.module('oauth.accessToken', []); accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken){ var service = { - token: null + token: null, + typedLogin: "", + typedPassword: "", + scope: "" }, hashFragmentKeys = [ //Oauth2 keys per http://tools.ietf.org/html/rfc6749#section-4.2.2 @@ -29,7 +32,12 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q * - takes the token from the fragment URI * - takes the token from the sessionStorage */ - service.set = function(scope) { + service.set = function(scope, typedLogin, typedPassword, oauthScope) { + if (typedLogin && typedPassword) { + service.typedLogin = typedLogin; + service.typedPassword = typedPassword; + service.scope = oauthScope; + } refreshTokenUri = scope.site + scope.tokenPath; if ($location.search().code) { return this.setTokenFromCode($location.search(), scope); @@ -129,11 +137,15 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q if (!params.refresh_token) { var deferred = $q.defer(); deferred.resolve(params); - $rootScope.$broadcast('oauth:login', token); + $rootScope.$broadcast('oauth:login', params); return deferred.promise; } else { return refreshToken(); } + } else { + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; } }; @@ -153,7 +165,31 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q setToken(result.data); $rootScope.$broadcast('oauth:login', service.token); }, function () { - $rootScope.$broadcast('oauth:expired', service.token); + if (service.typedLogin && service.typedPassword) { + return reconnect(); + } else { + $rootScope.$broadcast('oauth:expired', service.token); + } + }); + }; + + var reconnect = function () { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "password", username: service.typedLogin, password: service.typedPassword, scope: service.scope} + }).then(function (result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + }, function () { + $rootScope.$broadcast('oauth:denied'); }); }; diff --git a/app/views/templates/button.html b/app/views/templates/button.html index ce7861a..f30d62d 100644 --- a/app/views/templates/button.html +++ b/app/views/templates/button.html @@ -1,5 +1,25 @@ - Login Button - Logout {{profile.email}} - Access denied. + Login Button + Logout {{profile.email}} + Access denied. + + + +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
diff --git a/app/views/templates/default.html b/app/views/templates/default.html index dbd71f2..96dbe03 100644 --- a/app/views/templates/default.html +++ b/app/views/templates/default.html @@ -1,5 +1,25 @@ - {{text}} - Logout {{profile.email}} - Access denied. Try again. + {{text}} + Logout {{profile.email}} + Access denied. Try again. + + + +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 13cf466..0eb9ee6 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2017-02-12 */ +/* oauth-ng - v0.4.10 - 2017-02-13 */ 'use strict'; @@ -369,7 +369,10 @@ var accessTokenService = angular.module('oauth.accessToken', []); accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken){ var service = { - token: null + token: null, + typedLogin: "", + typedPassword: "", + scope: "" }, hashFragmentKeys = [ //Oauth2 keys per http://tools.ietf.org/html/rfc6749#section-4.2.2 @@ -393,7 +396,12 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q * - takes the token from the fragment URI * - takes the token from the sessionStorage */ - service.set = function(scope) { + service.set = function(scope, typedLogin, typedPassword, oauthScope) { + if (typedLogin && typedPassword) { + service.typedLogin = typedLogin; + service.typedPassword = typedPassword; + service.scope = oauthScope; + } refreshTokenUri = scope.site + scope.tokenPath; if ($location.search().code) { return this.setTokenFromCode($location.search(), scope); @@ -493,11 +501,15 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q if (!params.refresh_token) { var deferred = $q.defer(); deferred.resolve(params); - $rootScope.$broadcast('oauth:login', token); + $rootScope.$broadcast('oauth:login', params); return deferred.promise; } else { return refreshToken(); } + } else { + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; } }; @@ -517,7 +529,31 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q setToken(result.data); $rootScope.$broadcast('oauth:login', service.token); }, function () { - $rootScope.$broadcast('oauth:expired', service.token); + if (service.typedLogin && service.typedPassword) { + return reconnect(); + } else { + $rootScope.$broadcast('oauth:expired', service.token); + } + }); + }; + + var reconnect = function () { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "password", username: service.typedLogin, password: service.typedPassword, scope: service.scope} + }).then(function (result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + }, function () { + $rootScope.$broadcast('oauth:denied'); }); }; @@ -921,13 +957,13 @@ directives.directive('oauth', [ authorizePath: '@', // (optional) authorization url tokenPath: '@', // (optional) token 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 - // 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 - wellKnown: '@', // (optional for OpenID Connect) whether to load public key according to .well-known/openid-configuration endpoint + storage: '@', // (optional) Store token in 'sessionStorage' or 'localStorage', defaults to 'sessionStorage' + 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 + 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. } @@ -959,14 +995,17 @@ directives.directive('oauth', [ }; 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; - scope.storage = scope.storage || 'sessionStorage'; + 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; + scope.storage = scope.storage || 'sessionStorage'; + scope.typedLogin = ""; + scope.typedPassword = ""; + scope.typedKeepConnection = false; }; var compile = function() { @@ -989,7 +1028,7 @@ directives.directive('oauth', [ var initView = function () { var token = AccessToken.get(); - if (!token) { + if (!token && scope.responseType !== "password") { return scope.login(); } // without access token it's logged out, so we attempt to log in if (AccessToken.expired()) { @@ -1012,6 +1051,28 @@ directives.directive('oauth', [ $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; }; + + scope.checkPassword = function () { + $http({ + method: "POST", + url: scope.site + scope.tokenPath, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "password", username: scope.typedLogin, password: scope.typedPassword, scope: scope.scope} + }).then(function (result) { + AccessToken.set(result.data, scope.typedLogin, scope.typedPassword, scope.scope).then(function () { // sets the access token object (if existing, from fragment or session) + }); + $rootScope.$broadcast('oauth:login', result.data); + scope.show = "logged-in"; + }, function () { + $rootScope.$broadcast('oauth:denied'); + }); + }; scope.$on('oauth:expired',expired); @@ -1058,6 +1119,8 @@ directives.directive('oauth', [ scope.$on('$stateChangeSuccess', function () { $timeout(refreshDirective); }); + + }; return definition; diff --git a/dist/views/templates/button.html b/dist/views/templates/button.html index ce7861a..f30d62d 100644 --- a/dist/views/templates/button.html +++ b/dist/views/templates/button.html @@ -1,5 +1,25 @@ - Login Button - Logout {{profile.email}} - Access denied. + Login Button + Logout {{profile.email}} + Access denied. + + + +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
diff --git a/dist/views/templates/default.html b/dist/views/templates/default.html index dbd71f2..96dbe03 100644 --- a/dist/views/templates/default.html +++ b/dist/views/templates/default.html @@ -1,5 +1,25 @@ - {{text}} - Logout {{profile.email}} - Access denied. Try again. + {{text}} + Logout {{profile.email}} + Access denied. Try again. + + + +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
From a9d7dc821b8cc5c42987ec22f89199ce6ab1af12 Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Mon, 13 Feb 2017 17:14:55 -0500 Subject: [PATCH 05/11] Finish password response_type --- app/scripts/directives/oauth.js | 66 ++++++---- app/scripts/services/access-token.js | 117 +++++++++++------ app/scripts/services/endpoint.js | 4 +- dist/oauth-ng.js | 187 +++++++++++++++++---------- 4 files changed, 236 insertions(+), 138 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index e6af37d..59b5d18 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -21,26 +21,27 @@ directives.directive('oauth', [ 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, 'code' for authorization code flow and 'password' for resource owner password - 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 - tokenPath: '@', // (optional) token 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 - // 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 - 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. + 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, 'code' for authorization code flow and 'password' for resource owner password + 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 + tokenPath: '@', // (optional) token 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 + // 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 + 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. + disableCheckSession:'@' // (optional) can current token be checked ? } }; @@ -78,6 +79,7 @@ directives.directive('oauth', [ scope.state = scope.state || undefined; scope.scope = scope.scope || undefined; scope.storage = scope.storage || 'sessionStorage'; + scope.disableCheckSession = scope.disableCheckSession || false; scope.typedLogin = ""; scope.typedPassword = ""; scope.typedKeepConnection = false; @@ -122,9 +124,13 @@ directives.directive('oauth', [ }; scope.logout = function () { + scope.typedLogin = ""; + scope.typedPassword = ""; + scope.typedKeepConnection = false; Endpoint.logout(); $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; + AccessToken.destroy(); }; scope.checkPassword = function () { @@ -140,16 +146,21 @@ directives.directive('oauth', [ }, data: {grant_type: "password", username: scope.typedLogin, password: scope.typedPassword, scope: scope.scope} }).then(function (result) { - AccessToken.set(result.data, scope.typedLogin, scope.typedPassword, scope.scope).then(function () { // sets the access token object (if existing, from fragment or session) - }); - $rootScope.$broadcast('oauth:login', result.data); + if (scope.typedKeepConnection) { + AccessToken.setTokenFromPassword(scope, result.data, scope.typedLogin, scope.typedPassword, scope.scope); + } else { + AccessToken.setTokenFromPassword(scope, result.data); + scope.typedLogin = ""; + scope.typedPassword = ""; + scope.typedKeepConnection = false; + } scope.show = "logged-in"; }, function () { $rootScope.$broadcast('oauth:denied'); }); }; - scope.$on('oauth:expired',expired); + scope.$on('oauth:expired', expired); // user is authorized var authorized = function() { @@ -158,9 +169,13 @@ directives.directive('oauth', [ }; var expired = function() { - $rootScope.$broadcast('oauth:expired'); + scope.show = 'logged-out'; scope.logout(); }; + + scope.runExpired = function() { + expired(); + }; // set the oauth directive to the denied status var denied = function() { @@ -195,7 +210,6 @@ directives.directive('oauth', [ $timeout(refreshDirective); }); - }; return definition; diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 1c3ec95..4a4fb02 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -8,7 +8,8 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q token: null, typedLogin: "", typedPassword: "", - scope: "" + scope: "", + runExpired: null }, hashFragmentKeys = [ //Oauth2 keys per http://tools.ietf.org/html/rfc6749#section-4.2.2 @@ -29,16 +30,14 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q /** * Sets and returns the access token. It tries (in order) the following strategies: + * - Get the token using the code in the url * - takes the token from the fragment URI * - takes the token from the sessionStorage */ - service.set = function(scope, typedLogin, typedPassword, oauthScope) { - if (typedLogin && typedPassword) { - service.typedLogin = typedLogin; - service.typedPassword = typedPassword; - service.scope = oauthScope; - } + service.set = function(scope) { refreshTokenUri = scope.site + scope.tokenPath; + this.runExpired = scope.runExpired; + if ($location.search().code) { return this.setTokenFromCode($location.search(), scope); } @@ -61,12 +60,24 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q return deferred.promise; } }; + + service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { + this.runExpired = scope.runExpired; + if (typedLogin && typedPassword && oauthScope) { + service.typedLogin = typedLogin; + service.typedPassword = typedPassword; + service.scope = oauthScope; + } + setToken(token); + $rootScope.$broadcast('oauth:login', token); + } /** * Delete the access token and remove the session. * @returns {null} */ service.destroy = function(){ + cancelExpiresAtEvent(); Storage.delete('token'); this.token = null; return this.token; @@ -140,7 +151,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q $rootScope.$broadcast('oauth:login', params); return deferred.promise; } else { - return refreshToken(); + return refreshToken(true); } } else { var deferred = $q.defer(); @@ -149,28 +160,44 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q } }; - var refreshToken = function () { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} - }).then(function (result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); - }, function () { - if (service.typedLogin && service.typedPassword) { - return reconnect(); - } else { - $rootScope.$broadcast('oauth:expired', service.token); - } - }); + var refreshToken = function (connect) { + if (service.token && service.token.refresh_token) { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} + }).then(function (result) { + angular.extend(service.token, result.data); + setExpiresAt(); + setTokenInSession(); + if (connect) { + $rootScope.$broadcast('oauth:login', service.token); + } else { + $rootScope.$broadcast('oauth:refresh', service.token); + } + return result.data; + }, function () { + if (!!service.typedLogin && !!service.typedPassword) { + return reconnect(); + } else { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } + }); + } else { + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; + } }; var reconnect = function () { @@ -186,8 +213,9 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q }, data: {grant_type: "password", username: service.typedLogin, password: service.typedPassword, scope: service.scope} }).then(function (result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); + angular.extend(service.token, result.data); + setTokenInSession(); + $rootScope.$broadcast('oauth:refresh', service.token); }, function () { $rootScope.$broadcast('oauth:denied'); }); @@ -200,11 +228,11 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q * @returns {*|{}} */ var setToken = function(params){ - service.token = service.token || {}; // init the token - angular.extend(service.token, params); // set the access token params - setTokenInSession(); // save the token into the session + service.token = service.token || {}; // init the token + angular.extend(service.token, params); // set the access token params + setTokenInSession(); // save the token into the session setExpiresAt(); - setExpiresAtEvent(); // event to fire when the token expires + setExpiresAtEvent(); // event to fire when the token expires return service.token; }; @@ -238,7 +266,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q /** * Save the access token into the session */ - var setTokenInSession = function(){ + var setTokenInSession = function() { Storage.set('token', service.token); }; @@ -261,7 +289,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q /** - * Set the timeout at which the expired event is fired + * Set the interval at which the expired event is fired */ var setExpiresAtEvent = function(){ // Don't bother if there's no expires token @@ -276,16 +304,21 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q refreshToken(); }, time); } else { - expiresAtEvent = $interval(function() { - $rootScope.$broadcast('oauth:expired', service.token); + expiresAtEvent = $timeout(function() { + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); }, time, 1); } } }; var cancelExpiresAtEvent = function() { - if(expiresAtEvent && !service.token.refresh_token) { - $timeout.cancel(expiresAtEvent); + if(expiresAtEvent) { + if (service.token.refresh_token) { + $interval.cancel(expiresAtEvent); + } else { + $timeout.cancel(expiresAtEvent); + } expiresAtEvent = undefined; } }; diff --git a/app/scripts/services/endpoint.js b/app/scripts/services/endpoint.js index 6db8df6..f92fa5a 100644 --- a/app/scripts/services/endpoint.js +++ b/app/scripts/services/endpoint.js @@ -67,7 +67,7 @@ endpointClient.factory('Endpoint', ['$rootScope', 'AccessToken', '$q', '$http', */ service.checkValidity = function() { var params = service.config; - if( params.sessionPath ) { + if( params.sessionPath && !params.disableCheckSession ) { var token = AccessToken.get(); if( !token ) { return $q.reject("No token configured"); @@ -82,6 +82,8 @@ endpointClient.factory('Endpoint', ['$rootScope', 'AccessToken', '$q', '$http', return $q.reject("Server replied: token is invalid."); } }); + } else if (params.disableCheckSession) { + return true; } else { return $q.reject("You must give a :session-path param in order to validate the token.") } diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 0eb9ee6..96e6235 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -372,7 +372,8 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q token: null, typedLogin: "", typedPassword: "", - scope: "" + scope: "", + runExpired: null }, hashFragmentKeys = [ //Oauth2 keys per http://tools.ietf.org/html/rfc6749#section-4.2.2 @@ -393,16 +394,14 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q /** * Sets and returns the access token. It tries (in order) the following strategies: + * - Get the token using the code in the url * - takes the token from the fragment URI * - takes the token from the sessionStorage */ - service.set = function(scope, typedLogin, typedPassword, oauthScope) { - if (typedLogin && typedPassword) { - service.typedLogin = typedLogin; - service.typedPassword = typedPassword; - service.scope = oauthScope; - } + service.set = function(scope) { refreshTokenUri = scope.site + scope.tokenPath; + this.runExpired = scope.runExpired; + if ($location.search().code) { return this.setTokenFromCode($location.search(), scope); } @@ -425,12 +424,24 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q return deferred.promise; } }; + + service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { + this.runExpired = scope.runExpired; + if (typedLogin && typedPassword && oauthScope) { + service.typedLogin = typedLogin; + service.typedPassword = typedPassword; + service.scope = oauthScope; + } + setToken(token); + $rootScope.$broadcast('oauth:login', token); + } /** * Delete the access token and remove the session. * @returns {null} */ service.destroy = function(){ + cancelExpiresAtEvent(); Storage.delete('token'); this.token = null; return this.token; @@ -504,7 +515,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q $rootScope.$broadcast('oauth:login', params); return deferred.promise; } else { - return refreshToken(); + return refreshToken(true); } } else { var deferred = $q.defer(); @@ -513,28 +524,44 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q } }; - var refreshToken = function () { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} - }).then(function (result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); - }, function () { - if (service.typedLogin && service.typedPassword) { - return reconnect(); - } else { - $rootScope.$broadcast('oauth:expired', service.token); - } - }); + var refreshToken = function (connect) { + if (service.token && service.token.refresh_token) { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} + }).then(function (result) { + angular.extend(service.token, result.data); + setExpiresAt(); + setTokenInSession(); + if (connect) { + $rootScope.$broadcast('oauth:login', service.token); + } else { + $rootScope.$broadcast('oauth:refresh', service.token); + } + return result.data; + }, function () { + if (!!service.typedLogin && !!service.typedPassword) { + return reconnect(); + } else { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } + }); + } else { + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; + } }; var reconnect = function () { @@ -550,8 +577,9 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q }, data: {grant_type: "password", username: service.typedLogin, password: service.typedPassword, scope: service.scope} }).then(function (result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); + angular.extend(service.token, result.data); + setTokenInSession(); + $rootScope.$broadcast('oauth:refresh', service.token); }, function () { $rootScope.$broadcast('oauth:denied'); }); @@ -564,11 +592,11 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q * @returns {*|{}} */ var setToken = function(params){ - service.token = service.token || {}; // init the token - angular.extend(service.token, params); // set the access token params - setTokenInSession(); // save the token into the session + service.token = service.token || {}; // init the token + angular.extend(service.token, params); // set the access token params + setTokenInSession(); // save the token into the session setExpiresAt(); - setExpiresAtEvent(); // event to fire when the token expires + setExpiresAtEvent(); // event to fire when the token expires return service.token; }; @@ -602,7 +630,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q /** * Save the access token into the session */ - var setTokenInSession = function(){ + var setTokenInSession = function() { Storage.set('token', service.token); }; @@ -625,7 +653,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q /** - * Set the timeout at which the expired event is fired + * Set the interval at which the expired event is fired */ var setExpiresAtEvent = function(){ // Don't bother if there's no expires token @@ -640,16 +668,21 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q refreshToken(); }, time); } else { - expiresAtEvent = $interval(function() { - $rootScope.$broadcast('oauth:expired', service.token); + expiresAtEvent = $timeout(function() { + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); }, time, 1); } } }; var cancelExpiresAtEvent = function() { - if(expiresAtEvent && !service.token.refresh_token) { - $timeout.cancel(expiresAtEvent); + if(expiresAtEvent) { + if (service.token.refresh_token) { + $interval.cancel(expiresAtEvent); + } else { + $timeout.cancel(expiresAtEvent); + } expiresAtEvent = undefined; } }; @@ -740,7 +773,7 @@ endpointClient.factory('Endpoint', ['$rootScope', 'AccessToken', '$q', '$http', */ service.checkValidity = function() { var params = service.config; - if( params.sessionPath ) { + if( params.sessionPath && !params.disableCheckSession ) { var token = AccessToken.get(); if( !token ) { return $q.reject("No token configured"); @@ -755,6 +788,8 @@ endpointClient.factory('Endpoint', ['$rootScope', 'AccessToken', '$q', '$http', return $q.reject("Server replied: token is invalid."); } }); + } else if (params.disableCheckSession) { + return true; } else { return $q.reject("You must give a :session-path param in order to validate the token.") } @@ -946,26 +981,27 @@ directives.directive('oauth', [ 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, 'code' for authorization code flow and 'password' for resource owner password - 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 - tokenPath: '@', // (optional) token 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 - // 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 - 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. + 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, 'code' for authorization code flow and 'password' for resource owner password + 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 + tokenPath: '@', // (optional) token 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 + // 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 + 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. + disableCheckSession:'@' // (optional) can current token be checked ? } }; @@ -1003,6 +1039,7 @@ directives.directive('oauth', [ scope.state = scope.state || undefined; scope.scope = scope.scope || undefined; scope.storage = scope.storage || 'sessionStorage'; + scope.disableCheckSession = scope.disableCheckSession || false; scope.typedLogin = ""; scope.typedPassword = ""; scope.typedKeepConnection = false; @@ -1047,9 +1084,13 @@ directives.directive('oauth', [ }; scope.logout = function () { + scope.typedLogin = ""; + scope.typedPassword = ""; + scope.typedKeepConnection = false; Endpoint.logout(); $rootScope.$broadcast('oauth:loggedOut'); scope.show = 'logged-out'; + AccessToken.destroy(); }; scope.checkPassword = function () { @@ -1065,16 +1106,21 @@ directives.directive('oauth', [ }, data: {grant_type: "password", username: scope.typedLogin, password: scope.typedPassword, scope: scope.scope} }).then(function (result) { - AccessToken.set(result.data, scope.typedLogin, scope.typedPassword, scope.scope).then(function () { // sets the access token object (if existing, from fragment or session) - }); - $rootScope.$broadcast('oauth:login', result.data); + if (scope.typedKeepConnection) { + AccessToken.setTokenFromPassword(scope, result.data, scope.typedLogin, scope.typedPassword, scope.scope); + } else { + AccessToken.setTokenFromPassword(scope, result.data); + scope.typedLogin = ""; + scope.typedPassword = ""; + scope.typedKeepConnection = false; + } scope.show = "logged-in"; }, function () { $rootScope.$broadcast('oauth:denied'); }); }; - scope.$on('oauth:expired',expired); + scope.$on('oauth:expired', expired); // user is authorized var authorized = function() { @@ -1083,9 +1129,13 @@ directives.directive('oauth', [ }; var expired = function() { - $rootScope.$broadcast('oauth:expired'); + scope.show = 'logged-out'; scope.logout(); }; + + scope.runExpired = function() { + expired(); + }; // set the oauth directive to the denied status var denied = function() { @@ -1120,7 +1170,6 @@ directives.directive('oauth', [ $timeout(refreshDirective); }); - }; return definition; From 9b840864355ccb4349bcafb327216835bbc89721 Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Sat, 25 Feb 2017 13:29:05 -0500 Subject: [PATCH 06/11] Add signal oauth:external:refresh to force refresh token by the caller app, re-indent code --- app/scripts/directives/oauth.js | 4 + app/scripts/services/access-token.js | 670 ++++++++++++++------------- dist/oauth-ng.js | 642 +++++++++++++------------ 3 files changed, 684 insertions(+), 632 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 59b5d18..7edbbdb 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -210,6 +210,10 @@ directives.directive('oauth', [ $timeout(refreshDirective); }); + scope.$on("oauth:external:refresh", function () { + AccessToken.forceRefresh(0); + }); + }; return definition; diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 4a4fb02..8322553 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -2,340 +2,362 @@ var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken){ - - var service = { - token: null, - typedLogin: "", - typedPassword: "", - scope: "", - runExpired: null - }, - 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', - //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse - 'id_token' - ]; - var expiresAtEvent = null; - var refreshTokenUri = null; - - /** - * Returns the access token. - */ - service.get = function(){ - return this.token; - }; - - /** - * Sets and returns the access token. It tries (in order) the following strategies: - * - Get the token using the code in the url - * - takes the token from the fragment URI - * - takes the token from the sessionStorage - */ - service.set = function(scope) { - refreshTokenUri = scope.site + scope.tokenPath; - this.runExpired = scope.runExpired; - - if ($location.search().code) { - return this.setTokenFromCode($location.search(), scope); - } - - this.setTokenFromString($location.hash()); - - //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect - - var deferred = $q.defer(); - - if (this.token) { - deferred.resolve(this.token); - } else { - deferred.reject(); - } - - if(null === service.token) { - return setTokenFromSession(); - } else { - return deferred.promise; - } - }; - - service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { - this.runExpired = scope.runExpired; - if (typedLogin && typedPassword && oauthScope) { - service.typedLogin = typedLogin; - service.typedPassword = typedPassword; - service.scope = oauthScope; - } - setToken(token); - $rootScope.$broadcast('oauth:login', token); - } - - /** - * Delete the access token and remove the session. - * @returns {null} - */ - service.destroy = function(){ - cancelExpiresAtEvent(); - 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 && new Date(this.token.expires_at) < new Date()); - }; - - service.setTokenFromCode = function (search, scope) { - return $http({ - method: "POST", - url: scope.site + scope.tokenPath, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "authorization_code", code: search.code, redirect_uri: scope.redirectUri, client_id: scope.clientId} - }).then(function (result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); - $location.url($location.path()); - }); - } - - /** - * Get the access token from a string and save it - * @param hash - */ - service.setTokenFromString = function(hash){ - var params = getTokenFromString(hash); - - if(params){ - removeFragment(); - setToken(params); - // 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); - } - }; - - /** - * updates the expiration of the token - */ - service.updateExpiry = function(newExpiresIn){ - this.token.expires_in = newExpiresIn; - setExpiresAt(); - }; - - /* * * * * * * * * * - * PRIVATE METHODS * - * * * * * * * * * */ - - /** - * Set the access token from the sessionStorage. - */ - var setTokenFromSession = function() { - var params = Storage.get('token'); - if (params) { - setToken(params); - if (!params.refresh_token) { - var deferred = $q.defer(); - deferred.resolve(params); - $rootScope.$broadcast('oauth:login', params); - return deferred.promise; - } else { - return refreshToken(true); - } - } else { - var deferred = $q.defer(); - deferred.reject(); - return deferred.promise; - } - }; - - var refreshToken = function (connect) { - if (service.token && service.token.refresh_token) { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken) { + + var service = { + token: null, + typedLogin: "", + typedPassword: "", + scope: "", + runExpired: null }, - data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} - }).then(function (result) { - angular.extend(service.token, result.data); - setExpiresAt(); - setTokenInSession(); - if (connect) { - $rootScope.$broadcast('oauth:login', service.token); + 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', + //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + 'id_token' + ]; + var expiresAtEvent = null; + var refreshTokenUri = null; + + /** + * Returns the access token. + */ + service.get = function() { + return this.token; + }; + + /** + * Sets and returns the access token. It tries (in order) the following strategies: + * - Get the token using the code in the url + * - takes the token from the fragment URI + * - takes the token from the sessionStorage + */ + service.set = function(scope) { + refreshTokenUri = scope.site + scope.tokenPath; + this.runExpired = scope.runExpired; + + if ($location.search().code) { + return this.setTokenFromCode($location.search(), scope); + } + + this.setTokenFromString($location.hash()); + + //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect + + var deferred = $q.defer(); + + if (this.token) { + deferred.resolve(this.token); } else { - $rootScope.$broadcast('oauth:refresh', service.token); + deferred.reject(); } - return result.data; - }, function () { - if (!!service.typedLogin && !!service.typedPassword) { - return reconnect(); + + if (null === service.token) { + return setTokenFromSession(); } else { - cancelExpiresAtEvent(); - Storage.delete('token'); - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); + return deferred.promise; } - }); - } else { - var deferred = $q.defer(); - deferred.reject(); - return deferred.promise; - } - }; - - var reconnect = function () { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "password", username: service.typedLogin, password: service.typedPassword, scope: service.scope} - }).then(function (result) { - angular.extend(service.token, result.data); - setTokenInSession(); - $rootScope.$broadcast('oauth:refresh', service.token); - }, function () { - $rootScope.$broadcast('oauth:denied'); - }); - }; - - /** - * Set the access token. - * - * @param params - * @returns {*|{}} - */ - var setToken = function(params){ - service.token = service.token || {}; // init the token - angular.extend(service.token, params); // set the access token params - setTokenInSession(); // save the token into the session - setExpiresAt(); - setExpiresAtEvent(); // event to fire when the token expires - - return service.token; - }; - - /** - * Parse the fragment URI and return an object - * @param hash - * @returns {{}} - */ - var getTokenFromString = function(hash){ - var params = {}, - regex = /([^&=]+)=([^&]*)/g, - m; - - while ((m = regex.exec(hash)) !== null) { - params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); - } + }; - // OpenID Connect - if (params.id_token && !params.error) { - IdToken.validateTokensAndPopulateClaims(params); - return params; + service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { + this.runExpired = scope.runExpired; + if (typedLogin && typedPassword && oauthScope) { + service.typedLogin = typedLogin; + service.typedPassword = typedPassword; + service.scope = oauthScope; + } + setToken(token); + $rootScope.$broadcast('oauth:login', token); } - // Oauth2 - if(params.access_token || params.error){ - return params; - } - }; - - /** - * Save the access token into the session - */ - var setTokenInSession = function() { - Storage.set('token', service.token); - }; - - /** - * Set the access token expiration date (useful for refresh logics) - */ - var setExpiresAt = function(){ - 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; - } - }; + /** + * Delete the access token and remove the session. + * @returns {null} + */ + service.destroy = function() { + cancelExpiresAtEvent(); + 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 && new Date(this.token.expires_at) < new Date()); + }; - /** - * Set the interval 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; - } - cancelExpiresAtEvent(); - var time = (new Date(service.token.expires_at))-(new Date()); - if(time && time > 0 && time <= 2147483647) { - if (service.token.refresh_token) { - expiresAtEvent = $interval(function() { - refreshToken(); - }, time); - } else { - expiresAtEvent = $timeout(function() { - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); - }, time, 1); - } + service.setTokenFromCode = function(search, scope) { + return $http({ + method: "POST", + url: scope.site + scope.tokenPath, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + transformRequest: function(obj) { + var str = []; + for (var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: { + grant_type: "authorization_code", + code: search.code, + redirect_uri: scope.redirectUri, + client_id: scope.clientId + } + }).then(function(result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + $location.url($location.path()); + }); } - }; - - var cancelExpiresAtEvent = function() { - if(expiresAtEvent) { - if (service.token.refresh_token) { - $interval.cancel(expiresAtEvent); - } else { - $timeout.cancel(expiresAtEvent); - } - expiresAtEvent = undefined; - } - }; - - /** - * Remove the oAuth2 pieces from the hash fragment - */ - var removeFragment = function(){ - var curHash = $location.hash(); - angular.forEach(hashFragmentKeys,function(hashKey){ - var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?'); - curHash = curHash.replace(re,''); - }); - - $location.hash(curHash); - }; - - return service; + + /** + * Get the access token from a string and save it + * @param hash + */ + service.setTokenFromString = function(hash) { + var params = getTokenFromString(hash); + + if (params) { + removeFragment(); + setToken(params); + // 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); + } + }; + + /** + * updates the expiration of the token + */ + service.updateExpiry = function(newExpiresIn) { + this.token.expires_in = newExpiresIn; + setExpiresAt(); + }; + + service.forceRefresh = function(connect) { + refreshToken(connect); + }; + + /* * * * * * * * * * + * PRIVATE METHODS * + * * * * * * * * * */ + + /** + * Set the access token from the sessionStorage. + */ + var setTokenFromSession = function() { + var params = Storage.get('token'); + if (params) { + setToken(params); + if (!params.refresh_token) { + var deferred = $q.defer(); + deferred.resolve(params); + $rootScope.$broadcast('oauth:login', params); + return deferred.promise; + } else { + return refreshToken(true); + } + } else { + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; + } + }; + + var refreshToken = function(connect) { + if (service.token && service.token.refresh_token) { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + transformRequest: function(obj) { + var str = []; + for (var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: { + grant_type: "refresh_token", + refresh_token: service.token.refresh_token + } + }).then(function(result) { + angular.extend(service.token, result.data); + setExpiresAt(); + setTokenInSession(); + if (connect) { + $rootScope.$broadcast('oauth:login', service.token); + } else { + $rootScope.$broadcast('oauth:refresh', service.token); + } + return result.data; + }, function(error) { + if (!!service.typedLogin && !!service.typedPassword) { + return reconnect(); + } else { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } + }); + } else { + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; + } + }; + + var reconnect = function() { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + transformRequest: function(obj) { + var str = []; + for (var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: { + grant_type: "password", + username: service.typedLogin, + password: service.typedPassword, + scope: service.scope + } + }).then(function(result) { + angular.extend(service.token, result.data); + setTokenInSession(); + $rootScope.$broadcast('oauth:refresh', service.token); + }, function() { + $rootScope.$broadcast('oauth:denied'); + }); + }; + + /** + * Set the access token. + * + * @param params + * @returns {*|{}} + */ + var setToken = function(params) { + service.token = service.token || {}; // init the token + angular.extend(service.token, params); // set the access token params + setTokenInSession(); // save the token into the session + setExpiresAt(); + setExpiresAtEvent(); // event to fire when the token expires + + return service.token; + }; + + /** + * Parse the fragment URI and return an object + * @param hash + * @returns {{}} + */ + var getTokenFromString = function(hash) { + var params = {}, + regex = /([^&=]+)=([^&]*)/g, + m; + + while ((m = regex.exec(hash)) !== null) { + 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; + } + }; + + /** + * Save the access token into the session + */ + var setTokenInSession = function() { + Storage.set('token', service.token); + }; + + /** + * Set the access token expiration date (useful for refresh logics) + */ + var setExpiresAt = function() { + 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; + } + }; + + + /** + * Set the interval 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; + } + cancelExpiresAtEvent(); + var time = (new Date(service.token.expires_at)) - (new Date()); + if (time && time > 0 && time <= 2147483647) { + if (service.token.refresh_token) { + expiresAtEvent = $interval(function() { + refreshToken(); + }, time); + } else { + expiresAtEvent = $timeout(function() { + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + }, time, 1); + } + } + }; + + var cancelExpiresAtEvent = function() { + if (expiresAtEvent) { + if (service.token.refresh_token) { + $interval.cancel(expiresAtEvent); + } else { + $timeout.cancel(expiresAtEvent); + } + expiresAtEvent = undefined; + } + }; + + /** + * Remove the oAuth2 pieces from the hash fragment + */ + var removeFragment = function() { + var curHash = $location.hash(); + angular.forEach(hashFragmentKeys, function(hashKey) { + var re = new RegExp('&' + hashKey + '(=[^&]*)?|^' + hashKey + '(=[^&]*)?&?'); + curHash = curHash.replace(re, ''); + }); + + $location.hash(curHash); + }; + + return service; }]); diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 96e6235..7399d65 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2017-02-13 */ +/* oauth-ng - v0.4.10 - 2017-02-25 */ 'use strict'; @@ -366,341 +366,363 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken) { - var service = { - token: null, - typedLogin: "", - typedPassword: "", - scope: "", - runExpired: null - }, - 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', - //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse - 'id_token' - ]; - var expiresAtEvent = null; - var refreshTokenUri = null; + var service = { + token: null, + typedLogin: "", + typedPassword: "", + scope: "", + runExpired: null + }, + 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', + //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + 'id_token' + ]; + var expiresAtEvent = null; + var refreshTokenUri = null; + + /** + * Returns the access token. + */ + service.get = function() { + return this.token; + }; - /** - * Returns the access token. - */ - service.get = function(){ - return this.token; - }; + /** + * Sets and returns the access token. It tries (in order) the following strategies: + * - Get the token using the code in the url + * - takes the token from the fragment URI + * - takes the token from the sessionStorage + */ + service.set = function(scope) { + refreshTokenUri = scope.site + scope.tokenPath; + this.runExpired = scope.runExpired; - /** - * Sets and returns the access token. It tries (in order) the following strategies: - * - Get the token using the code in the url - * - takes the token from the fragment URI - * - takes the token from the sessionStorage - */ - service.set = function(scope) { - refreshTokenUri = scope.site + scope.tokenPath; - this.runExpired = scope.runExpired; - - if ($location.search().code) { - return this.setTokenFromCode($location.search(), scope); - } - - this.setTokenFromString($location.hash()); - - //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect - - var deferred = $q.defer(); - - if (this.token) { - deferred.resolve(this.token); - } else { - deferred.reject(); - } - - if(null === service.token) { - return setTokenFromSession(); - } else { - return deferred.promise; - } - }; - - service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { - this.runExpired = scope.runExpired; - if (typedLogin && typedPassword && oauthScope) { - service.typedLogin = typedLogin; - service.typedPassword = typedPassword; - service.scope = oauthScope; - } - setToken(token); - $rootScope.$broadcast('oauth:login', token); - } + if ($location.search().code) { + return this.setTokenFromCode($location.search(), scope); + } - /** - * Delete the access token and remove the session. - * @returns {null} - */ - service.destroy = function(){ - cancelExpiresAtEvent(); - Storage.delete('token'); - this.token = null; - return this.token; - }; + this.setTokenFromString($location.hash()); - /** - * Tells if the access token is expired. - */ - service.expired = function(){ - return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); - }; - - service.setTokenFromCode = function (search, scope) { - return $http({ - method: "POST", - url: scope.site + scope.tokenPath, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "authorization_code", code: search.code, redirect_uri: scope.redirectUri, client_id: scope.clientId} - }).then(function (result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); - $location.url($location.path()); - }); - } + //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect - /** - * Get the access token from a string and save it - * @param hash - */ - service.setTokenFromString = function(hash){ - var params = getTokenFromString(hash); - - if(params){ - removeFragment(); - setToken(params); - // 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); + var deferred = $q.defer(); + + if (this.token) { + deferred.resolve(this.token); + } else { + deferred.reject(); + } + + if (null === service.token) { + return setTokenFromSession(); + } else { + return deferred.promise; + } + }; + + service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { + this.runExpired = scope.runExpired; + if (typedLogin && typedPassword && oauthScope) { + service.typedLogin = typedLogin; + service.typedPassword = typedPassword; + service.scope = oauthScope; + } + setToken(token); + $rootScope.$broadcast('oauth:login', token); } - }; - /** - * updates the expiration of the token - */ - service.updateExpiry = function(newExpiresIn){ - this.token.expires_in = newExpiresIn; - setExpiresAt(); - }; + /** + * Delete the access token and remove the session. + * @returns {null} + */ + service.destroy = function() { + cancelExpiresAtEvent(); + Storage.delete('token'); + this.token = null; + return this.token; + }; - /* * * * * * * * * * - * PRIVATE METHODS * - * * * * * * * * * */ + /** + * Tells if the access token is expired. + */ + service.expired = function() { + return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); + }; - /** - * Set the access token from the sessionStorage. - */ - var setTokenFromSession = function() { - var params = Storage.get('token'); - if (params) { - setToken(params); - if (!params.refresh_token) { - var deferred = $q.defer(); - deferred.resolve(params); - $rootScope.$broadcast('oauth:login', params); - return deferred.promise; - } else { - return refreshToken(true); - } - } else { - var deferred = $q.defer(); - deferred.reject(); - return deferred.promise; + service.setTokenFromCode = function(search, scope) { + return $http({ + method: "POST", + url: scope.site + scope.tokenPath, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + transformRequest: function(obj) { + var str = []; + for (var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: { + grant_type: "authorization_code", + code: search.code, + redirect_uri: scope.redirectUri, + client_id: scope.clientId + } + }).then(function(result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + $location.url($location.path()); + }); } - }; - - var refreshToken = function (connect) { - if (service.token && service.token.refresh_token) { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} - }).then(function (result) { - angular.extend(service.token, result.data); + + /** + * Get the access token from a string and save it + * @param hash + */ + service.setTokenFromString = function(hash) { + var params = getTokenFromString(hash); + + if (params) { + removeFragment(); + setToken(params); + // 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); + } + }; + + /** + * updates the expiration of the token + */ + service.updateExpiry = function(newExpiresIn) { + this.token.expires_in = newExpiresIn; setExpiresAt(); - setTokenInSession(); - if (connect) { - $rootScope.$broadcast('oauth:login', service.token); + }; + + service.forceRefresh = function(connect) { + refreshToken(connect); + }; + + /* * * * * * * * * * + * PRIVATE METHODS * + * * * * * * * * * */ + + /** + * Set the access token from the sessionStorage. + */ + var setTokenFromSession = function() { + var params = Storage.get('token'); + if (params) { + setToken(params); + if (!params.refresh_token) { + var deferred = $q.defer(); + deferred.resolve(params); + $rootScope.$broadcast('oauth:login', params); + return deferred.promise; + } else { + return refreshToken(true); + } } else { - $rootScope.$broadcast('oauth:refresh', service.token); + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; } - return result.data; - }, function () { - if (!!service.typedLogin && !!service.typedPassword) { - return reconnect(); + }; + + var refreshToken = function(connect) { + if (service.token && service.token.refresh_token) { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + transformRequest: function(obj) { + var str = []; + for (var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: { + grant_type: "refresh_token", + refresh_token: service.token.refresh_token + } + }).then(function(result) { + angular.extend(service.token, result.data); + setExpiresAt(); + setTokenInSession(); + if (connect) { + $rootScope.$broadcast('oauth:login', service.token); + } else { + $rootScope.$broadcast('oauth:refresh', service.token); + } + return result.data; + }, function(error) { + if (!!service.typedLogin && !!service.typedPassword) { + return reconnect(); + } else { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } + }); } else { - cancelExpiresAtEvent(); - Storage.delete('token'); - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; } - }); - } else { - var deferred = $q.defer(); - deferred.reject(); - return deferred.promise; - } - }; - - var reconnect = function () { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "password", username: service.typedLogin, password: service.typedPassword, scope: service.scope} - }).then(function (result) { - angular.extend(service.token, result.data); - setTokenInSession(); - $rootScope.$broadcast('oauth:refresh', service.token); - }, function () { - $rootScope.$broadcast('oauth:denied'); - }); - }; + }; - /** - * Set the access token. - * - * @param params - * @returns {*|{}} - */ - var setToken = function(params){ - service.token = service.token || {}; // init the token - angular.extend(service.token, params); // set the access token params - setTokenInSession(); // save the token into the session - setExpiresAt(); - setExpiresAtEvent(); // event to fire when the token expires - - return service.token; - }; + var reconnect = function() { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + transformRequest: function(obj) { + var str = []; + for (var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: { + grant_type: "password", + username: service.typedLogin, + password: service.typedPassword, + scope: service.scope + } + }).then(function(result) { + angular.extend(service.token, result.data); + setTokenInSession(); + $rootScope.$broadcast('oauth:refresh', service.token); + }, function() { + $rootScope.$broadcast('oauth:denied'); + }); + }; - /** - * Parse the fragment URI and return an object - * @param hash - * @returns {{}} - */ - var getTokenFromString = function(hash){ - var params = {}, - regex = /([^&=]+)=([^&]*)/g, - m; + /** + * Set the access token. + * + * @param params + * @returns {*|{}} + */ + var setToken = function(params) { + service.token = service.token || {}; // init the token + angular.extend(service.token, params); // set the access token params + setTokenInSession(); // save the token into the session + setExpiresAt(); + setExpiresAtEvent(); // event to fire when the token expires - while ((m = regex.exec(hash)) !== null) { - params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); - } + return service.token; + }; - // OpenID Connect - if (params.id_token && !params.error) { - IdToken.validateTokensAndPopulateClaims(params); - return params; - } + /** + * Parse the fragment URI and return an object + * @param hash + * @returns {{}} + */ + var getTokenFromString = function(hash) { + var params = {}, + regex = /([^&=]+)=([^&]*)/g, + m; - // Oauth2 - if(params.access_token || params.error){ - return params; - } - }; + while ((m = regex.exec(hash)) !== null) { + params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); + } - /** - * Save the access token into the session - */ - var setTokenInSession = function() { - Storage.set('token', service.token); - }; + // OpenID Connect + if (params.id_token && !params.error) { + IdToken.validateTokensAndPopulateClaims(params); + return params; + } - /** - * Set the access token expiration date (useful for refresh logics) - */ - var setExpiresAt = function(){ - 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; - } - }; + // Oauth2 + if (params.access_token || params.error) { + return params; + } + }; + + /** + * Save the access token into the session + */ + var setTokenInSession = function() { + Storage.set('token', service.token); + }; + /** + * Set the access token expiration date (useful for refresh logics) + */ + var setExpiresAt = function() { + 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; + } + }; - /** - * Set the interval 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; - } - cancelExpiresAtEvent(); - var time = (new Date(service.token.expires_at))-(new Date()); - if(time && time > 0 && time <= 2147483647) { - if (service.token.refresh_token) { - expiresAtEvent = $interval(function() { - refreshToken(); - }, time); - } else { - expiresAtEvent = $timeout(function() { - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); - }, time, 1); - } - } - }; - var cancelExpiresAtEvent = function() { - if(expiresAtEvent) { - if (service.token.refresh_token) { - $interval.cancel(expiresAtEvent); - } else { - $timeout.cancel(expiresAtEvent); - } - expiresAtEvent = undefined; - } - }; + /** + * Set the interval 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; + } + cancelExpiresAtEvent(); + var time = (new Date(service.token.expires_at)) - (new Date()); + if (time && time > 0 && time <= 2147483647) { + if (service.token.refresh_token) { + expiresAtEvent = $interval(function() { + refreshToken(); + }, time); + } else { + expiresAtEvent = $timeout(function() { + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + }, time, 1); + } + } + }; - /** - * Remove the oAuth2 pieces from the hash fragment - */ - var removeFragment = function(){ - var curHash = $location.hash(); - angular.forEach(hashFragmentKeys,function(hashKey){ - var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?'); - curHash = curHash.replace(re,''); - }); - - $location.hash(curHash); - }; + var cancelExpiresAtEvent = function() { + if (expiresAtEvent) { + if (service.token.refresh_token) { + $interval.cancel(expiresAtEvent); + } else { + $timeout.cancel(expiresAtEvent); + } + expiresAtEvent = undefined; + } + }; - return service; + /** + * Remove the oAuth2 pieces from the hash fragment + */ + var removeFragment = function() { + var curHash = $location.hash(); + angular.forEach(hashFragmentKeys, function(hashKey) { + var re = new RegExp('&' + hashKey + '(=[^&]*)?|^' + hashKey + '(=[^&]*)?&?'); + curHash = curHash.replace(re, ''); + }); + + $location.hash(curHash); + }; + + return service; }]); @@ -1170,6 +1192,10 @@ directives.directive('oauth', [ $timeout(refreshDirective); }); + scope.$on("oauth:external:refresh", function () { + AccessToken.forceRefresh(0); + }); + }; return definition; From 26095d8a1cfcf6250c620f00f4337b21a60b43ce Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Sat, 7 Oct 2017 17:48:01 -0400 Subject: [PATCH 07/11] New build --- 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 7399d65..4079dae 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2017-02-25 */ +/* oauth-ng - v0.4.10 - 2017-10-07 */ 'use strict'; From 904397bc3d9c2cec43b370dc39783908e5d029fd Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Tue, 20 Feb 2018 14:01:41 -0500 Subject: [PATCH 08/11] Do not automatically redirect to login url if not connected --- app/scripts/directives/oauth.js | 6 +- dist/oauth-ng.js | 644 +++++++++++++++----------------- 2 files changed, 310 insertions(+), 340 deletions(-) diff --git a/app/scripts/directives/oauth.js b/app/scripts/directives/oauth.js index 7edbbdb..91fd5d7 100644 --- a/app/scripts/directives/oauth.js +++ b/app/scripts/directives/oauth.js @@ -106,7 +106,7 @@ directives.directive('oauth', [ var token = AccessToken.get(); if (!token && scope.responseType !== "password") { - return scope.login(); + return expired(); } // without access token it's logged out, so we attempt to log in if (AccessToken.expired()) { return expired(); @@ -210,10 +210,6 @@ directives.directive('oauth', [ $timeout(refreshDirective); }); - scope.$on("oauth:external:refresh", function () { - AccessToken.forceRefresh(0); - }); - }; return definition; diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 4079dae..687bdb3 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2017-10-07 */ +/* oauth-ng - v0.4.10 - 2018-02-19 */ 'use strict'; @@ -366,363 +366,341 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken) { +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken){ - var service = { - token: null, - typedLogin: "", - typedPassword: "", - scope: "", - runExpired: null - }, - 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', - //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse - 'id_token' - ]; - var expiresAtEvent = null; - var refreshTokenUri = null; - - /** - * Returns the access token. - */ - service.get = function() { - return this.token; - }; - - /** - * Sets and returns the access token. It tries (in order) the following strategies: - * - Get the token using the code in the url - * - takes the token from the fragment URI - * - takes the token from the sessionStorage - */ - service.set = function(scope) { - refreshTokenUri = scope.site + scope.tokenPath; - this.runExpired = scope.runExpired; - - if ($location.search().code) { - return this.setTokenFromCode($location.search(), scope); - } - - this.setTokenFromString($location.hash()); + var service = { + token: null, + typedLogin: "", + typedPassword: "", + scope: "", + runExpired: null + }, + 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', + //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + 'id_token' + ]; + var expiresAtEvent = null; + var refreshTokenUri = null; - //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect + /** + * Returns the access token. + */ + service.get = function(){ + return this.token; + }; - var deferred = $q.defer(); + /** + * Sets and returns the access token. It tries (in order) the following strategies: + * - Get the token using the code in the url + * - takes the token from the fragment URI + * - takes the token from the sessionStorage + */ + service.set = function(scope) { + refreshTokenUri = scope.site + scope.tokenPath; + this.runExpired = scope.runExpired; + + if ($location.search().code) { + return this.setTokenFromCode($location.search(), scope); + } + + this.setTokenFromString($location.hash()); + + //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect + + var deferred = $q.defer(); + + if (this.token) { + deferred.resolve(this.token); + } else { + deferred.reject(); + } + + if(null === service.token) { + return setTokenFromSession(); + } else { + return deferred.promise; + } + }; + + service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { + this.runExpired = scope.runExpired; + if (typedLogin && typedPassword && oauthScope) { + service.typedLogin = typedLogin; + service.typedPassword = typedPassword; + service.scope = oauthScope; + } + setToken(token); + $rootScope.$broadcast('oauth:login', token); + } - if (this.token) { - deferred.resolve(this.token); - } else { - deferred.reject(); - } + /** + * Delete the access token and remove the session. + * @returns {null} + */ + service.destroy = function(){ + cancelExpiresAtEvent(); + Storage.delete('token'); + this.token = null; + return this.token; + }; - if (null === service.token) { - return setTokenFromSession(); - } else { - return deferred.promise; - } - }; + /** + * Tells if the access token is expired. + */ + service.expired = function(){ + return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); + }; + + service.setTokenFromCode = function (search, scope) { + return $http({ + method: "POST", + url: scope.site + scope.tokenPath, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "authorization_code", code: search.code, redirect_uri: scope.redirectUri, client_id: scope.clientId} + }).then(function (result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + $location.url($location.path()); + }); + } - service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { - this.runExpired = scope.runExpired; - if (typedLogin && typedPassword && oauthScope) { - service.typedLogin = typedLogin; - service.typedPassword = typedPassword; - service.scope = oauthScope; - } - setToken(token); - $rootScope.$broadcast('oauth:login', token); + /** + * Get the access token from a string and save it + * @param hash + */ + service.setTokenFromString = function(hash){ + var params = getTokenFromString(hash); + + if(params){ + removeFragment(); + setToken(params); + // 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); } + }; - /** - * Delete the access token and remove the session. - * @returns {null} - */ - service.destroy = function() { - cancelExpiresAtEvent(); - Storage.delete('token'); - this.token = null; - return this.token; - }; + /** + * updates the expiration of the token + */ + service.updateExpiry = function(newExpiresIn){ + this.token.expires_in = newExpiresIn; + setExpiresAt(); + }; - /** - * Tells if the access token is expired. - */ - service.expired = function() { - return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); - }; + /* * * * * * * * * * + * PRIVATE METHODS * + * * * * * * * * * */ - service.setTokenFromCode = function(search, scope) { - return $http({ - method: "POST", - url: scope.site + scope.tokenPath, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - transformRequest: function(obj) { - var str = []; - for (var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: { - grant_type: "authorization_code", - code: search.code, - redirect_uri: scope.redirectUri, - client_id: scope.clientId - } - }).then(function(result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); - $location.url($location.path()); - }); + /** + * Set the access token from the sessionStorage. + */ + var setTokenFromSession = function() { + var params = Storage.get('token'); + if (params) { + setToken(params); + if (!params.refresh_token) { + var deferred = $q.defer(); + deferred.resolve(params); + $rootScope.$broadcast('oauth:login', params); + return deferred.promise; + } else { + return refreshToken(true); + } + } else { + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; } - - /** - * Get the access token from a string and save it - * @param hash - */ - service.setTokenFromString = function(hash) { - var params = getTokenFromString(hash); - - if (params) { - removeFragment(); - setToken(params); - // 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); - } - }; - - /** - * updates the expiration of the token - */ - service.updateExpiry = function(newExpiresIn) { - this.token.expires_in = newExpiresIn; + }; + + var refreshToken = function (connect) { + if (service.token && service.token.refresh_token) { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} + }).then(function (result) { + angular.extend(service.token, result.data); setExpiresAt(); - }; - - service.forceRefresh = function(connect) { - refreshToken(connect); - }; - - /* * * * * * * * * * - * PRIVATE METHODS * - * * * * * * * * * */ - - /** - * Set the access token from the sessionStorage. - */ - var setTokenFromSession = function() { - var params = Storage.get('token'); - if (params) { - setToken(params); - if (!params.refresh_token) { - var deferred = $q.defer(); - deferred.resolve(params); - $rootScope.$broadcast('oauth:login', params); - return deferred.promise; - } else { - return refreshToken(true); - } + setTokenInSession(); + if (connect) { + $rootScope.$broadcast('oauth:login', service.token); } else { - var deferred = $q.defer(); - deferred.reject(); - return deferred.promise; + $rootScope.$broadcast('oauth:refresh', service.token); } - }; - - var refreshToken = function(connect) { - if (service.token && service.token.refresh_token) { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - transformRequest: function(obj) { - var str = []; - for (var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: { - grant_type: "refresh_token", - refresh_token: service.token.refresh_token - } - }).then(function(result) { - angular.extend(service.token, result.data); - setExpiresAt(); - setTokenInSession(); - if (connect) { - $rootScope.$broadcast('oauth:login', service.token); - } else { - $rootScope.$broadcast('oauth:refresh', service.token); - } - return result.data; - }, function(error) { - if (!!service.typedLogin && !!service.typedPassword) { - return reconnect(); - } else { - cancelExpiresAtEvent(); - Storage.delete('token'); - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); - } - }); + return result.data; + }, function () { + if (!!service.typedLogin && !!service.typedPassword) { + return reconnect(); } else { - var deferred = $q.defer(); - deferred.reject(); - return deferred.promise; + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); } - }; - - var reconnect = function() { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - transformRequest: function(obj) { - var str = []; - for (var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: { - grant_type: "password", - username: service.typedLogin, - password: service.typedPassword, - scope: service.scope - } - }).then(function(result) { - angular.extend(service.token, result.data); - setTokenInSession(); - $rootScope.$broadcast('oauth:refresh', service.token); - }, function() { - $rootScope.$broadcast('oauth:denied'); - }); - }; - - /** - * Set the access token. - * - * @param params - * @returns {*|{}} - */ - var setToken = function(params) { - service.token = service.token || {}; // init the token - angular.extend(service.token, params); // set the access token params - setTokenInSession(); // save the token into the session - setExpiresAt(); - setExpiresAtEvent(); // event to fire when the token expires - - return service.token; - }; + }); + } else { + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; + } + }; + + var reconnect = function () { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + transformRequest: function(obj) { + var str = []; + for(var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: {grant_type: "password", username: service.typedLogin, password: service.typedPassword, scope: service.scope} + }).then(function (result) { + angular.extend(service.token, result.data); + setTokenInSession(); + $rootScope.$broadcast('oauth:refresh', service.token); + }, function () { + $rootScope.$broadcast('oauth:denied'); + }); + }; - /** - * Parse the fragment URI and return an object - * @param hash - * @returns {{}} - */ - var getTokenFromString = function(hash) { - var params = {}, - regex = /([^&=]+)=([^&]*)/g, - m; + /** + * Set the access token. + * + * @param params + * @returns {*|{}} + */ + var setToken = function(params){ + service.token = service.token || {}; // init the token + angular.extend(service.token, params); // set the access token params + setTokenInSession(); // save the token into the session + setExpiresAt(); + setExpiresAtEvent(); // event to fire when the token expires + + return service.token; + }; - while ((m = regex.exec(hash)) !== null) { - params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); - } + /** + * Parse the fragment URI and return an object + * @param hash + * @returns {{}} + */ + var getTokenFromString = function(hash){ + var params = {}, + regex = /([^&=]+)=([^&]*)/g, + m; - // OpenID Connect - if (params.id_token && !params.error) { - IdToken.validateTokensAndPopulateClaims(params); - return params; - } + while ((m = regex.exec(hash)) !== null) { + params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); + } - // Oauth2 - if (params.access_token || params.error) { - return params; - } - }; + // OpenID Connect + if (params.id_token && !params.error) { + IdToken.validateTokensAndPopulateClaims(params); + return params; + } - /** - * Save the access token into the session - */ - var setTokenInSession = function() { - Storage.set('token', service.token); - }; + // Oauth2 + if(params.access_token || params.error){ + return params; + } + }; - /** - * Set the access token expiration date (useful for refresh logics) - */ - var setExpiresAt = function() { - 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; - } - }; + /** + * Save the access token into the session + */ + var setTokenInSession = function() { + Storage.set('token', service.token); + }; + /** + * Set the access token expiration date (useful for refresh logics) + */ + var setExpiresAt = function(){ + 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; + } + }; - /** - * Set the interval 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; - } - cancelExpiresAtEvent(); - var time = (new Date(service.token.expires_at)) - (new Date()); - if (time && time > 0 && time <= 2147483647) { - if (service.token.refresh_token) { - expiresAtEvent = $interval(function() { - refreshToken(); - }, time); - } else { - expiresAtEvent = $timeout(function() { - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); - }, time, 1); - } - } - }; - var cancelExpiresAtEvent = function() { - if (expiresAtEvent) { - if (service.token.refresh_token) { - $interval.cancel(expiresAtEvent); - } else { - $timeout.cancel(expiresAtEvent); - } - expiresAtEvent = undefined; - } - }; + /** + * Set the interval 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; + } + cancelExpiresAtEvent(); + var time = (new Date(service.token.expires_at))-(new Date()); + if(time && time > 0 && time <= 2147483647) { + if (service.token.refresh_token) { + expiresAtEvent = $interval(function() { + refreshToken(); + }, time); + } else { + expiresAtEvent = $timeout(function() { + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + }, time, 1); + } + } + }; - /** - * Remove the oAuth2 pieces from the hash fragment - */ - var removeFragment = function() { - var curHash = $location.hash(); - angular.forEach(hashFragmentKeys, function(hashKey) { - var re = new RegExp('&' + hashKey + '(=[^&]*)?|^' + hashKey + '(=[^&]*)?&?'); - curHash = curHash.replace(re, ''); - }); + var cancelExpiresAtEvent = function() { + if(expiresAtEvent) { + if (service.token.refresh_token) { + $interval.cancel(expiresAtEvent); + } else { + $timeout.cancel(expiresAtEvent); + } + expiresAtEvent = undefined; + } + }; - $location.hash(curHash); - }; + /** + * Remove the oAuth2 pieces from the hash fragment + */ + var removeFragment = function(){ + var curHash = $location.hash(); + angular.forEach(hashFragmentKeys,function(hashKey){ + var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?'); + curHash = curHash.replace(re,''); + }); + + $location.hash(curHash); + }; - return service; + return service; }]); @@ -1088,7 +1066,7 @@ directives.directive('oauth', [ var token = AccessToken.get(); if (!token && scope.responseType !== "password") { - return scope.login(); + return expired(); } // without access token it's logged out, so we attempt to log in if (AccessToken.expired()) { return expired(); @@ -1192,10 +1170,6 @@ directives.directive('oauth', [ $timeout(refreshDirective); }); - scope.$on("oauth:external:refresh", function () { - AccessToken.forceRefresh(0); - }); - }; return definition; From a5992dea191c0a3d7ee34f97a29dc0be2a3eddff Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Sat, 24 Feb 2018 14:48:09 -0500 Subject: [PATCH 09/11] Update compiled --- dist/oauth-ng.js | 638 ++++++++++++++++++++++++----------------------- 1 file changed, 330 insertions(+), 308 deletions(-) diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 687bdb3..0ac69ab 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2018-02-19 */ +/* oauth-ng - v0.4.10 - 2018-02-24 */ 'use strict'; @@ -366,341 +366,363 @@ idTokenService.factory('IdToken', ['Storage', function(Storage) { var accessTokenService = angular.module('oauth.accessToken', []); -accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken){ +accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q', '$location', '$interval', '$timeout', 'IdToken', function(Storage, $rootScope, $http, $q, $location, $interval, $timeout, IdToken) { - var service = { - token: null, - typedLogin: "", - typedPassword: "", - scope: "", - runExpired: null - }, - 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', - //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse - 'id_token' - ]; - var expiresAtEvent = null; - var refreshTokenUri = null; + var service = { + token: null, + typedLogin: "", + typedPassword: "", + scope: "", + runExpired: null + }, + 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', + //Additional OpenID Connect key per http://openid.net/specs/openid-connect-core-1_0.html#ImplicitAuthResponse + 'id_token' + ]; + var expiresAtEvent = null; + var refreshTokenUri = null; + + /** + * Returns the access token. + */ + service.get = function() { + return this.token; + }; - /** - * Returns the access token. - */ - service.get = function(){ - return this.token; - }; + /** + * Sets and returns the access token. It tries (in order) the following strategies: + * - Get the token using the code in the url + * - takes the token from the fragment URI + * - takes the token from the sessionStorage + */ + service.set = function(scope) { + refreshTokenUri = scope.site + scope.tokenPath; + this.runExpired = scope.runExpired; - /** - * Sets and returns the access token. It tries (in order) the following strategies: - * - Get the token using the code in the url - * - takes the token from the fragment URI - * - takes the token from the sessionStorage - */ - service.set = function(scope) { - refreshTokenUri = scope.site + scope.tokenPath; - this.runExpired = scope.runExpired; - - if ($location.search().code) { - return this.setTokenFromCode($location.search(), scope); - } - - this.setTokenFromString($location.hash()); - - //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect - - var deferred = $q.defer(); - - if (this.token) { - deferred.resolve(this.token); - } else { - deferred.reject(); - } - - if(null === service.token) { - return setTokenFromSession(); - } else { - return deferred.promise; - } - }; - - service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { - this.runExpired = scope.runExpired; - if (typedLogin && typedPassword && oauthScope) { - service.typedLogin = typedLogin; - service.typedPassword = typedPassword; - service.scope = oauthScope; - } - setToken(token); - $rootScope.$broadcast('oauth:login', token); - } + if ($location.search().code) { + return this.setTokenFromCode($location.search(), scope); + } - /** - * Delete the access token and remove the session. - * @returns {null} - */ - service.destroy = function(){ - cancelExpiresAtEvent(); - Storage.delete('token'); - this.token = null; - return this.token; - }; + this.setTokenFromString($location.hash()); - /** - * Tells if the access token is expired. - */ - service.expired = function(){ - return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); - }; - - service.setTokenFromCode = function (search, scope) { - return $http({ - method: "POST", - url: scope.site + scope.tokenPath, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "authorization_code", code: search.code, redirect_uri: scope.redirectUri, client_id: scope.clientId} - }).then(function (result) { - setToken(result.data); - $rootScope.$broadcast('oauth:login', service.token); - $location.url($location.path()); - }); - } + //If hash is present in URL always use it, cuz its coming from oAuth2 provider redirect - /** - * Get the access token from a string and save it - * @param hash - */ - service.setTokenFromString = function(hash){ - var params = getTokenFromString(hash); - - if(params){ - removeFragment(); - setToken(params); - // 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); + var deferred = $q.defer(); + + if (this.token) { + deferred.resolve(this.token); + } else { + deferred.reject(); + } + + if (null === service.token) { + return setTokenFromSession(); + } else { + return deferred.promise; + } + }; + + service.setTokenFromPassword = function(scope, token, typedLogin, typedPassword, oauthScope) { + this.runExpired = scope.runExpired; + if (typedLogin && typedPassword && oauthScope) { + service.typedLogin = typedLogin; + service.typedPassword = typedPassword; + service.scope = oauthScope; + } + setToken(token); + $rootScope.$broadcast('oauth:login', token); } - }; - /** - * updates the expiration of the token - */ - service.updateExpiry = function(newExpiresIn){ - this.token.expires_in = newExpiresIn; - setExpiresAt(); - }; + /** + * Delete the access token and remove the session. + * @returns {null} + */ + service.destroy = function() { + cancelExpiresAtEvent(); + Storage.delete('token'); + this.token = null; + return this.token; + }; - /* * * * * * * * * * - * PRIVATE METHODS * - * * * * * * * * * */ + /** + * Tells if the access token is expired. + */ + service.expired = function() { + return (this.token && this.token.expires_at && new Date(this.token.expires_at) < new Date()); + }; - /** - * Set the access token from the sessionStorage. - */ - var setTokenFromSession = function() { - var params = Storage.get('token'); - if (params) { - setToken(params); - if (!params.refresh_token) { - var deferred = $q.defer(); - deferred.resolve(params); - $rootScope.$broadcast('oauth:login', params); - return deferred.promise; - } else { - return refreshToken(true); - } - } else { - var deferred = $q.defer(); - deferred.reject(); - return deferred.promise; + service.setTokenFromCode = function(search, scope) { + return $http({ + method: "POST", + url: scope.site + scope.tokenPath, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + transformRequest: function(obj) { + var str = []; + for (var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: { + grant_type: "authorization_code", + code: search.code, + redirect_uri: scope.redirectUri, + client_id: scope.clientId + } + }).then(function(result) { + setToken(result.data); + $rootScope.$broadcast('oauth:login', service.token); + $location.url($location.path()); + }); } - }; - - var refreshToken = function (connect) { - if (service.token && service.token.refresh_token) { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "refresh_token", refresh_token: service.token.refresh_token} - }).then(function (result) { - angular.extend(service.token, result.data); + + /** + * Get the access token from a string and save it + * @param hash + */ + service.setTokenFromString = function(hash) { + var params = getTokenFromString(hash); + + if (params) { + removeFragment(); + setToken(params); + // 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); + } + }; + + /** + * updates the expiration of the token + */ + service.updateExpiry = function(newExpiresIn) { + this.token.expires_in = newExpiresIn; setExpiresAt(); - setTokenInSession(); - if (connect) { - $rootScope.$broadcast('oauth:login', service.token); + }; + + service.forceRefresh = function(connect) { + refreshToken(connect); + }; + + /* * * * * * * * * * + * PRIVATE METHODS * + * * * * * * * * * */ + + /** + * Set the access token from the sessionStorage. + */ + var setTokenFromSession = function() { + var params = Storage.get('token'); + if (params) { + setToken(params); + if (!params.refresh_token) { + var deferred = $q.defer(); + deferred.resolve(params); + $rootScope.$broadcast('oauth:login', params); + return deferred.promise; + } else { + return refreshToken(true); + } } else { - $rootScope.$broadcast('oauth:refresh', service.token); + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; } - return result.data; - }, function () { - if (!!service.typedLogin && !!service.typedPassword) { - return reconnect(); + }; + + var refreshToken = function(connect) { + if (service.token && service.token.refresh_token) { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + transformRequest: function(obj) { + var str = []; + for (var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: { + grant_type: "refresh_token", + refresh_token: service.token.refresh_token + } + }).then(function(result) { + angular.extend(service.token, result.data); + setExpiresAt(); + setTokenInSession(); + if (connect) { + $rootScope.$broadcast('oauth:login', service.token); + } else { + $rootScope.$broadcast('oauth:refresh', service.token); + } + return result.data; + }, function(error) { + if (!!service.typedLogin && !!service.typedPassword) { + return reconnect(); + } else { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } + }); } else { - cancelExpiresAtEvent(); - Storage.delete('token'); - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); + var deferred = $q.defer(); + deferred.reject(); + return deferred.promise; } - }); - } else { - var deferred = $q.defer(); - deferred.reject(); - return deferred.promise; - } - }; - - var reconnect = function () { - return $http({ - method: "POST", - url: refreshTokenUri, - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - transformRequest: function(obj) { - var str = []; - for(var p in obj) - str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); - return str.join("&"); - }, - data: {grant_type: "password", username: service.typedLogin, password: service.typedPassword, scope: service.scope} - }).then(function (result) { - angular.extend(service.token, result.data); - setTokenInSession(); - $rootScope.$broadcast('oauth:refresh', service.token); - }, function () { - $rootScope.$broadcast('oauth:denied'); - }); - }; + }; - /** - * Set the access token. - * - * @param params - * @returns {*|{}} - */ - var setToken = function(params){ - service.token = service.token || {}; // init the token - angular.extend(service.token, params); // set the access token params - setTokenInSession(); // save the token into the session - setExpiresAt(); - setExpiresAtEvent(); // event to fire when the token expires - - return service.token; - }; + var reconnect = function() { + return $http({ + method: "POST", + url: refreshTokenUri, + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + transformRequest: function(obj) { + var str = []; + for (var p in obj) + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); + return str.join("&"); + }, + data: { + grant_type: "password", + username: service.typedLogin, + password: service.typedPassword, + scope: service.scope + } + }).then(function(result) { + angular.extend(service.token, result.data); + setTokenInSession(); + $rootScope.$broadcast('oauth:refresh', service.token); + }, function() { + $rootScope.$broadcast('oauth:denied'); + }); + }; - /** - * Parse the fragment URI and return an object - * @param hash - * @returns {{}} - */ - var getTokenFromString = function(hash){ - var params = {}, - regex = /([^&=]+)=([^&]*)/g, - m; + /** + * Set the access token. + * + * @param params + * @returns {*|{}} + */ + var setToken = function(params) { + service.token = service.token || {}; // init the token + angular.extend(service.token, params); // set the access token params + setTokenInSession(); // save the token into the session + setExpiresAt(); + setExpiresAtEvent(); // event to fire when the token expires - while ((m = regex.exec(hash)) !== null) { - params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); - } + return service.token; + }; - // OpenID Connect - if (params.id_token && !params.error) { - IdToken.validateTokensAndPopulateClaims(params); - return params; - } + /** + * Parse the fragment URI and return an object + * @param hash + * @returns {{}} + */ + var getTokenFromString = function(hash) { + var params = {}, + regex = /([^&=]+)=([^&]*)/g, + m; - // Oauth2 - if(params.access_token || params.error){ - return params; - } - }; + while ((m = regex.exec(hash)) !== null) { + params[decodeURIComponent(m[1])] = decodeURIComponent(m[2]); + } - /** - * Save the access token into the session - */ - var setTokenInSession = function() { - Storage.set('token', service.token); - }; + // OpenID Connect + if (params.id_token && !params.error) { + IdToken.validateTokensAndPopulateClaims(params); + return params; + } - /** - * Set the access token expiration date (useful for refresh logics) - */ - var setExpiresAt = function(){ - 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; - } - }; + // Oauth2 + if (params.access_token || params.error) { + return params; + } + }; + /** + * Save the access token into the session + */ + var setTokenInSession = function() { + Storage.set('token', service.token); + }; - /** - * Set the interval 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; - } - cancelExpiresAtEvent(); - var time = (new Date(service.token.expires_at))-(new Date()); - if(time && time > 0 && time <= 2147483647) { - if (service.token.refresh_token) { - expiresAtEvent = $interval(function() { - refreshToken(); - }, time); - } else { - expiresAtEvent = $timeout(function() { - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); - }, time, 1); - } - } - }; + /** + * Set the access token expiration date (useful for refresh logics) + */ + var setExpiresAt = function() { + 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; + } + }; - var cancelExpiresAtEvent = function() { - if(expiresAtEvent) { - if (service.token.refresh_token) { - $interval.cancel(expiresAtEvent); - } else { - $timeout.cancel(expiresAtEvent); - } - expiresAtEvent = undefined; - } - }; - /** - * Remove the oAuth2 pieces from the hash fragment - */ - var removeFragment = function(){ - var curHash = $location.hash(); - angular.forEach(hashFragmentKeys,function(hashKey){ - var re = new RegExp('&'+hashKey+'(=[^&]*)?|^'+hashKey+'(=[^&]*)?&?'); - curHash = curHash.replace(re,''); - }); - - $location.hash(curHash); - }; + /** + * Set the interval 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; + } + cancelExpiresAtEvent(); + var time = (new Date(service.token.expires_at)) - (new Date()); + if (time && time > 0 && time <= 2147483647) { + if (service.token.refresh_token) { + expiresAtEvent = $interval(function() { + refreshToken(); + }, time); + } else { + expiresAtEvent = $timeout(function() { + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + }, time, 1); + } + } + }; - return service; + var cancelExpiresAtEvent = function() { + if (expiresAtEvent) { + if (service.token.refresh_token) { + $interval.cancel(expiresAtEvent); + } else { + $timeout.cancel(expiresAtEvent); + } + expiresAtEvent = undefined; + } + }; + + /** + * Remove the oAuth2 pieces from the hash fragment + */ + var removeFragment = function() { + var curHash = $location.hash(); + angular.forEach(hashFragmentKeys, function(hashKey) { + var re = new RegExp('&' + hashKey + '(=[^&]*)?|^' + hashKey + '(=[^&]*)?&?'); + curHash = curHash.replace(re, ''); + }); + + $location.hash(curHash); + }; + + return service; }]); From 75d1375412f011896ed67173f6e1f1b1ed141f82 Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Fri, 2 Mar 2018 13:30:32 -0500 Subject: [PATCH 10/11] Handle error status on refresh token event --- app/scripts/services/access-token.js | 12 +++++++----- dist/oauth-ng.js | 14 ++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 8322553..4763a62 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -142,7 +142,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q }; service.forceRefresh = function(connect) { - refreshToken(connect); + return refreshToken(connect); }; /* * * * * * * * * * @@ -203,10 +203,12 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q if (!!service.typedLogin && !!service.typedPassword) { return reconnect(); } else { - cancelExpiresAtEvent(); - Storage.delete('token'); - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); + if (error.status === 401 || error.status === 400) { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } } }); } else { diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index 0ac69ab..bfe2ad5 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2018-02-24 */ +/* oauth-ng - v0.4.10 - 2018-03-02 */ 'use strict'; @@ -506,7 +506,7 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q }; service.forceRefresh = function(connect) { - refreshToken(connect); + return refreshToken(connect); }; /* * * * * * * * * * @@ -567,10 +567,12 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q if (!!service.typedLogin && !!service.typedPassword) { return reconnect(); } else { - cancelExpiresAtEvent(); - Storage.delete('token'); - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); + if (error.status === 401 || error.status === 400) { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } } }); } else { From aed81057e455fd9a1e6fcc616024496c9684442b Mon Sep 17 00:00:00 2001 From: Nicolas Mora Date: Sat, 30 Jun 2018 07:29:45 -0400 Subject: [PATCH 11/11] Disable refresh token only if server answered an error 40x --- app/scripts/services/access-token.js | 25 +++++++++++++++++-------- dist/oauth-ng.js | 27 ++++++++++++++++++--------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/app/scripts/services/access-token.js b/app/scripts/services/access-token.js index 4763a62..f5365ab 100644 --- a/app/scripts/services/access-token.js +++ b/app/scripts/services/access-token.js @@ -203,12 +203,12 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q if (!!service.typedLogin && !!service.typedPassword) { return reconnect(); } else { - if (error.status === 401 || error.status === 400) { - cancelExpiresAtEvent(); - Storage.delete('token'); - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); - } + if (error.status === 401 || error.status === 400) { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } } }); } else { @@ -241,8 +241,17 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q angular.extend(service.token, result.data); setTokenInSession(); $rootScope.$broadcast('oauth:refresh', service.token); - }, function() { - $rootScope.$broadcast('oauth:denied'); + }, function(error) { + if (!!service.typedLogin && !!service.typedPassword) { + return reconnect(); + } else { + if (error.status === 401 || error.status === 400) { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } + } }); }; diff --git a/dist/oauth-ng.js b/dist/oauth-ng.js index bfe2ad5..6d3f630 100644 --- a/dist/oauth-ng.js +++ b/dist/oauth-ng.js @@ -1,4 +1,4 @@ -/* oauth-ng - v0.4.10 - 2018-03-02 */ +/* oauth-ng - v0.4.10 - 2018-06-30 */ 'use strict'; @@ -567,12 +567,12 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q if (!!service.typedLogin && !!service.typedPassword) { return reconnect(); } else { - if (error.status === 401 || error.status === 400) { - cancelExpiresAtEvent(); - Storage.delete('token'); - $rootScope.$broadcast('oauth:expired'); - service.runExpired(); - } + if (error.status === 401 || error.status === 400) { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } } }); } else { @@ -605,8 +605,17 @@ accessTokenService.factory('AccessToken', ['Storage', '$rootScope', '$http', '$q angular.extend(service.token, result.data); setTokenInSession(); $rootScope.$broadcast('oauth:refresh', service.token); - }, function() { - $rootScope.$broadcast('oauth:denied'); + }, function(error) { + if (!!service.typedLogin && !!service.typedPassword) { + return reconnect(); + } else { + if (error.status === 401 || error.status === 400) { + cancelExpiresAtEvent(); + Storage.delete('token'); + $rootScope.$broadcast('oauth:expired'); + service.runExpired(); + } + } }); };