diff --git a/bower.json b/bower.json index a0cfe69..7c95492 100644 --- a/bower.json +++ b/bower.json @@ -4,7 +4,7 @@ "authors": [ "Volodymyr Lavrynovych " ], - "version": "0.7.1", + "version": "1.0.0", "description": "Provides ability to easily handle most of the logic related to the authentication process and page load for the AngularJS SPA", "main": "dist/angular-spa-auth.min.js", "keywords": [ diff --git a/dist/angular-spa-auth.js b/dist/angular-spa-auth.js index ac5f3b5..f9972ef 100644 --- a/dist/angular-spa-auth.js +++ b/dist/angular-spa-auth.js @@ -49,7 +49,7 @@ return $rootScope.currentUser !== undefined; } }]) - .service('AuthService', ['$rootScope', '$q', '$http', '$location', function ($rootScope, $q, $http, $location) { + .service('AuthService', ['$rootScope', '$q', '$http', '$location', '$route', function ($rootScope, $q, $http, $location, $route) { // ------------------------------------------------------------------------/// Config var config = { @@ -137,7 +137,7 @@ // ------------------------------------------------------------------------/// Private function info(message) { - _log(console.info, message) + _log(console.info, 'AuthService: ' + message) } function error(err) { @@ -151,8 +151,15 @@ } function goTo(route) { + info(($location.path() || 'unknown') + ' -----> ' + route); if(route == config.uiRoutes.login && service.isAuthenticated()) { - $location.path(getHome()); + info('User is already authenticated and cannot open login page: ' + route); + route = getHome(); + $location.path(route); + info('Redirected to the ' + route); + } else if(route == $location.path()) { + info('Reload: ' + route); + $route.reload() } else { $location.path(route); info('Redirected to the ' + route); @@ -173,6 +180,7 @@ } function init() { + info('init'); isAuthenticated() .then(service.refreshCurrentUser) .then(function (user) { @@ -235,6 +243,7 @@ */ openTarget: function () { var target = config.uiRoutes.target || getHome(); + info('Open target: ' + target); goTo(target); service.clearTarget() }, diff --git a/dist/angular-spa-auth.min.js b/dist/angular-spa-auth.min.js index ddb40a0..daeba2c 100644 --- a/dist/angular-spa-auth.min.js +++ b/dist/angular-spa-auth.min.js @@ -1,2 +1,2 @@ -"use strict";!function(){var t={UNAUTHORIZED_REDIRECT_TO_LOGIN:"Unauthorized: redirecting to the login page",MISSING_CURRENT_USER_ENDPOINT:"Endpoint for current user is not specified",MISSING_LOGIN_ENDPOINT:"Login endpoint is not specified",MISSING_LOGOUT_ENDPOINT:"Logout endpoint is not specified",SUCCESS_AUTH:"Successfully authenticated"};angular.module("angular-spa-auth",["ngRoute"]).run(["$rootScope","$location","AuthService",function(n,e,r){function o(){return void 0!==n.currentUser}r.saveTarget(),n.$on("$routeChangeStart",function(n,i){function u(){return i.$$route&&!r.isPublic(i.$$route.originalPath)}function s(){r._info("Stop loading: "+e.path()),n.preventDefault()}r._info("Start loading: "+e.path()),o()?r.isAuthenticated()?e.path()==r.config.uiRoutes.login&&(s(),r.openHome()):u()&&(s(),r._info(t.UNAUTHORIZED_REDIRECT_TO_LOGIN),r.openLogin()):u()&&(r._info("Unknown user status"),s())}),n.$on("$routeChangeSuccess",function(t,n){r._info("Loaded: "+e.path())})}]).service("AuthService",["$rootScope","$q","$http","$location",function(n,e,r,o){function i(t){s(console.info,t)}function u(t){s(console.error,t)}function s(t,n){p.verbose&&t&&t(n)}function c(t){t==p.uiRoutes.login&&d.isAuthenticated()?o.path(h()):(o.path(t),i("Redirected to the "+t))}function a(){return p.endpoints.isAuthenticated?r.get(p.endpoints.isAuthenticated).then(function(t){var r=JSON.parse(t.data);return i("isAuthenticated: "+r),n.currentUser=t.data,r||e.reject(t.data)}):e.resolve(!0)}function l(){a().then(d.refreshCurrentUser).then(function(t){p.handlers.success(t)})["catch"](function(t){return f(),g(t)})}function f(){c(p.uiRoutes.login)}function g(t){return u(t),p.handlers.error(t),e.reject(t)}function h(){return p.handlers.getHomePage(n.currentUser)}var p={verbose:!1,publicUrls:["/login","/home"],endpoints:{isAuthenticated:null,currentUser:null,logout:"/logout",login:"/login"},uiRoutes:{login:"/login",home:"/home",target:null},handlers:{getHomePage:function(t){return p.uiRoutes.home},getUser:function(){if(!p.endpoints.currentUser)throw new Error(t.MISSING_CURRENT_USER_ENDPOINT);return r.get(p.endpoints.currentUser).then(function(t){return i("Current user: "+JSON.stringify(t.data)),t.data})},login:function(n){if(!p.endpoints.login)throw new Error(t.MISSING_LOGIN_ENDPOINT);return r.post(p.endpoints.login,n)},logout:function(){if(!p.endpoints.logout)throw new Error(t.MISSING_LOGOUT_ENDPOINT);return r.get(p.endpoints.logout).then(function(){n.currentUser=null,f()})},success:function(n){i(t.SUCCESS_AUTH)},error:u}},d={config:p,_info:i,isPublic:function(t){return t?p.publicUrls.some(function(n){return n instanceof RegExp?t.match(n):"string"==typeof n?t&&n.startsWith(t):!1}):!1},saveTarget:function(){p.uiRoutes.target=o.path()||null,i("Target route is saved: "+p.uiRoutes.target)},openTarget:function(){var t=p.uiRoutes.target||h();c(t),d.clearTarget()},clearTarget:function(){p.uiRoutes.target=null},openLogin:f,openHome:function(){c(h())},getCurrentUser:function(){return n.currentUser?e.resolve(n.currentUser):d.refreshCurrentUser()},refreshCurrentUser:function(){return p.handlers.getUser().then(function(t){return n.currentUser=t,d.openTarget(),n.currentUser})},isAuthenticated:function(){return!!n.currentUser},logout:function(){return p.handlers.logout()},run:function(t){t&&(t.publicUrls&&(p.publicUrls=t.publicUrls),p=angular.merge(p,t)),l()},login:function(t){return p.handlers.login(t).then(d.refreshCurrentUser).then(p.handlers.success)["catch"](g)}};return d}])}(),String.prototype.startsWith||!function(){var t=function(){try{var t={},n=Object.defineProperty,e=n(t,t,t)&&n}catch(r){}return e}(),n={}.toString,e=function(t){if(null==this)throw TypeError();var e=String(this);if(t&&"[object RegExp]"==n.call(t))throw TypeError();var r=e.length,o=String(t),i=o.length,u=arguments.length>1?arguments[1]:void 0,s=u?Number(u):0;s!=s&&(s=0);var c=Math.min(Math.max(s,0),r);if(i+c>r)return!1;for(var a=-1;++a "+t),t==d.uiRoutes.login&&U.isAuthenticated()?(u("User is already authenticated and cannot open login page: "+t),t=p(),o.path(t),u("Redirected to the "+t)):t==o.path()?(u("Reload: "+t),i.reload()):(o.path(t),u("Redirected to the "+t))}function l(){return d.endpoints.isAuthenticated?r.get(d.endpoints.isAuthenticated).then(function(t){var r=JSON.parse(t.data);return u("isAuthenticated: "+r),e.currentUser=t.data,r||n.reject(t.data)}):n.resolve(!0)}function g(){u("init"),l().then(U.refreshCurrentUser).then(function(t){d.handlers.success(t)})["catch"](function(t){return h(),f(t)})}function h(){a(d.uiRoutes.login)}function f(t){return c(t),d.handlers.error(t),n.reject(t)}function p(){return d.handlers.getHomePage(e.currentUser)}var d={verbose:!1,publicUrls:["/login","/home"],endpoints:{isAuthenticated:null,currentUser:null,logout:"/logout",login:"/login"},uiRoutes:{login:"/login",home:"/home",target:null},handlers:{getHomePage:function(t){return d.uiRoutes.home},getUser:function(){if(!d.endpoints.currentUser)throw new Error(t.MISSING_CURRENT_USER_ENDPOINT);return r.get(d.endpoints.currentUser).then(function(t){return u("Current user: "+JSON.stringify(t.data)),t.data})},login:function(e){if(!d.endpoints.login)throw new Error(t.MISSING_LOGIN_ENDPOINT);return r.post(d.endpoints.login,e)},logout:function(){if(!d.endpoints.logout)throw new Error(t.MISSING_LOGOUT_ENDPOINT);return r.get(d.endpoints.logout).then(function(){e.currentUser=null,h()})},success:function(e){u(t.SUCCESS_AUTH)},error:c}},U={config:d,_info:u,isPublic:function(t){return t?d.publicUrls.some(function(e){return e instanceof RegExp?t.match(e):"string"==typeof e?t&&e.startsWith(t):!1}):!1},saveTarget:function(){d.uiRoutes.target=o.path()||null,u("Target route is saved: "+d.uiRoutes.target)},openTarget:function(){var t=d.uiRoutes.target||p();u("Open target: "+t),a(t),U.clearTarget()},clearTarget:function(){d.uiRoutes.target=null},openLogin:h,openHome:function(){a(p())},getCurrentUser:function(){return e.currentUser?n.resolve(e.currentUser):U.refreshCurrentUser()},refreshCurrentUser:function(){return d.handlers.getUser().then(function(t){return e.currentUser=t,U.openTarget(),e.currentUser})},isAuthenticated:function(){return!!e.currentUser},logout:function(){return d.handlers.logout()},run:function(t){t&&(t.publicUrls&&(d.publicUrls=t.publicUrls),d=angular.merge(d,t)),g()},login:function(t){return d.handlers.login(t).then(U.refreshCurrentUser).then(d.handlers.success)["catch"](f)}};return U}])}(),String.prototype.startsWith||!function(){var t=function(){try{var t={},e=Object.defineProperty,n=e(t,t,t)&&e}catch(r){}return n}(),e={}.toString,n=function(t){if(null==this)throw TypeError();var n=String(this);if(t&&"[object RegExp]"==e.call(t))throw TypeError();var r=n.length,o=String(t),i=o.length,u=arguments.length>1?arguments[1]:void 0,c=u?Number(u):0;c!=c&&(c=0);var s=Math.min(Math.max(c,0),r);if(i+s>r)return!1;for(var a=-1;++a 1 ? arguments[1] : undefined;\n\t\t\t// `ToInteger`\n\t\t\tvar pos = position ? Number(position) : 0;\n\t\t\tif (pos != pos) { // better `isNaN`\n\t\t\t\tpos = 0;\n\t\t\t}\n\t\t\tvar start = Math.min(Math.max(pos, 0), stringLength);\n\t\t\t// Avoid the `indexOf` call if no match is possible\n\t\t\tif (searchLength + start > stringLength) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar index = -1;\n\t\t\twhile (++index < searchLength) {\n\t\t\t\tif (string.charCodeAt(start + index) != searchString.charCodeAt(index)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t};\n\t\tif (defineProperty) {\n\t\t\tdefineProperty(String.prototype, 'startsWith', {\n\t\t\t\t'value': startsWith,\n\t\t\t\t'configurable': true,\n\t\t\t\t'writable': true\n\t\t\t});\n\t\t} else {\n\t\t\tString.prototype.startsWith = startsWith;\n\t\t}\n\t}());\n}\n"]} \ No newline at end of file +{"version":3,"sources":["../src/angular-spa-auth.js","../node_modules/string.prototype.startswith/startswith.js"],"names":["MESSAGES","UNAUTHORIZED_REDIRECT_TO_LOGIN","MISSING_CURRENT_USER_ENDPOINT","MISSING_LOGIN_ENDPOINT","MISSING_LOGOUT_ENDPOINT","SUCCESS_AUTH","angular","module","run","$rootScope","$location","AuthService","isKnownStatus","undefined","currentUser","saveTarget","$on","event","next","isPrivate","$$route","isPublic","originalPath","stop","_info","path","preventDefault","isAuthenticated","config","uiRoutes","login","openHome","openLogin","service","$q","$http","$route","info","message","_log","console","error","err","fn","msg","verbose","goTo","route","getHome","reload","endpoints","get","then","response","isAuth","JSON","parse","data","reject","resolve","init","refreshCurrentUser","user","handlers","success","onError","getHomePage","publicUrls","logout","home","target","getUser","Error","stringify","credentials","post","some","pattern","RegExp","match","startsWith","openTarget","clearTarget","getCurrentUser","configuration","merge","String","prototype","defineProperty","object","$defineProperty","Object","result","toString","search","this","TypeError","string","call","stringLength","length","searchString","searchLength","position","arguments","pos","Number","start","Math","min","max","index","charCodeAt","value","configurable","writable"],"mappings":"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","file":"angular-spa-auth.min.js","sourcesContent":["'use strict';\n(function () {\n\n var MESSAGES = {\n UNAUTHORIZED_REDIRECT_TO_LOGIN: 'Unauthorized: redirecting to the login page',\n MISSING_CURRENT_USER_ENDPOINT: 'Endpoint for current user is not specified',\n MISSING_LOGIN_ENDPOINT: 'Login endpoint is not specified',\n MISSING_LOGOUT_ENDPOINT: 'Logout endpoint is not specified',\n SUCCESS_AUTH: 'Successfully authenticated'\n };\n\n angular.module('angular-spa-auth', ['ngRoute'])\n .run(['$rootScope', '$location', 'AuthService', function ($rootScope, $location, AuthService) {\n AuthService.saveTarget();\n $rootScope.$on('$routeChangeStart', function (event, next) {\n AuthService._info('Start loading: ' + $location.path());\n\n if(isKnownStatus()) {\n if (AuthService.isAuthenticated()) {\n if($location.path() == AuthService.config.uiRoutes.login) {\n stop();\n AuthService.openHome();\n }\n } else if (isPrivate()) {\n stop();\n AuthService._info(MESSAGES.UNAUTHORIZED_REDIRECT_TO_LOGIN);\n AuthService.openLogin();\n }\n } else if (isPrivate()) {\n AuthService._info('Unknown user status');\n stop();\n }\n\n function isPrivate() {\n return next.$$route && !AuthService.isPublic(next.$$route.originalPath)\n }\n\n function stop() {\n AuthService._info('Stop loading: ' + $location.path());\n event.preventDefault();\n }\n });\n\n $rootScope.$on('$routeChangeSuccess', function (event, next) {\n AuthService._info('Loaded: ' + $location.path());\n });\n\n function isKnownStatus() {\n return $rootScope.currentUser !== undefined;\n }\n }])\n .service('AuthService', ['$rootScope', '$q', '$http', '$location', '$route', function ($rootScope, $q, $http, $location, $route) {\n\n // ------------------------------------------------------------------------/// Config\n var config = {\n verbose: false,\n publicUrls: ['/login', '/home'],\n endpoints: {\n isAuthenticated: null,\n currentUser: null,\n logout: '/logout',\n login: '/login'\n },\n uiRoutes: {\n login: '/login',\n home: '/home',\n target: null\n },\n handlers: {\n /**\n * Returns url of home page as a string\n * @param {Object} user authenticated user\n * @returns {string} url to the default/home page\n */\n getHomePage: function(user) {\n return config.uiRoutes.home;\n },\n\n /**\n * Returns promise of GET request which should get current user from backend\n * @returns {Promise}\n */\n getUser: function () {\n if(!config.endpoints.currentUser) {\n throw new Error(MESSAGES.MISSING_CURRENT_USER_ENDPOINT)\n }\n\n return $http.get(config.endpoints.currentUser).then(function (response) {\n info('Current user: ' + JSON.stringify(response.data));\n return response.data\n })\n },\n\n /**\n * Tries to login user using provided credentials.\n * Sends GET request\n *\n * @param {Object} credentials object with user credentials\n * @param {String} [credentials.login]\n * @param {String} [credentials.password]\n * @returns {Promise}\n */\n login: function (credentials) {\n if(!config.endpoints.login) {\n throw new Error(MESSAGES.MISSING_LOGIN_ENDPOINT)\n }\n\n return $http.post(config.endpoints.login, credentials);\n },\n\n logout: function () {\n if(!config.endpoints.logout) {\n throw new Error(MESSAGES.MISSING_LOGOUT_ENDPOINT)\n }\n\n return $http.get(config.endpoints.logout).then(function () {\n $rootScope.currentUser = null;\n openLogin();\n });\n },\n\n /**\n * Success handler\n * @param {*} data received from backend\n */\n success: function (data) {\n info(MESSAGES.SUCCESS_AUTH)\n },\n\n /**\n * Error handler\n * @param {*} err backend error object\n */\n error: error\n }\n };\n\n // ------------------------------------------------------------------------/// Private\n function info(message) {\n _log(console.info, 'AuthService: ' + message)\n }\n\n function error(err) {\n _log(console.error, err);\n }\n \n function _log(fn, msg) {\n if(config.verbose) {\n fn && fn(msg)\n }\n }\n\n function goTo(route) {\n info(($location.path() || 'unknown') + ' -----> ' + route);\n if(route == config.uiRoutes.login && service.isAuthenticated()) {\n info('User is already authenticated and cannot open login page: ' + route);\n route = getHome();\n $location.path(route);\n info('Redirected to the ' + route);\n } else if(route == $location.path()) {\n info('Reload: ' + route);\n $route.reload()\n } else {\n $location.path(route);\n info('Redirected to the ' + route);\n }\n }\n\n function isAuthenticated() {\n if (!config.endpoints.isAuthenticated) {\n return $q.resolve(true);\n }\n\n return $http.get(config.endpoints.isAuthenticated).then(function (response) {\n var isAuth = JSON.parse(response.data);\n info('isAuthenticated: ' + isAuth);\n $rootScope.currentUser = response.data;\n return isAuth || $q.reject(response.data);\n });\n }\n\n function init() {\n info('init');\n isAuthenticated()\n .then(service.refreshCurrentUser)\n .then(function (user) {\n config.handlers.success(user);\n })\n .catch(function (err) {\n openLogin();\n return onError(err);\n });\n }\n\n function openLogin() {\n goTo(config.uiRoutes.login);\n }\n\n function onError(err) {\n error(err);\n config.handlers.error(err);\n return $q.reject(err);\n }\n\n function getHome() {\n return config.handlers.getHomePage($rootScope.currentUser);\n }\n\n // ------------------------------------------------------------------------/// Public\n var service = {\n config: config,\n _info: info,\n\n /**\n * Returns true if provide route url is in the list of public urls\n * @param {String} path route path that should be checked\n * @returns {boolean} true if url is in the list of public urls\n */\n isPublic: function (path) {\n if(!path) {\n return false;\n }\n\n return config.publicUrls.some(function (pattern) {\n if(pattern instanceof RegExp) {\n return path.match(pattern);\n } else if(typeof pattern == \"string\") {\n return path && pattern.startsWith(path);\n } else {\n return false;\n }\n });\n },\n /**\n * Saves current route as a target route\n */\n saveTarget: function () {\n config.uiRoutes.target = $location.path() || null;\n info('Target route is saved: ' + config.uiRoutes.target);\n },\n /**\n * Redirects user to the saved target route if exists or to the home page\n */\n openTarget: function () {\n var target = config.uiRoutes.target || getHome();\n info('Open target: ' + target);\n goTo(target);\n service.clearTarget()\n },\n /**\n * Clears saved target route\n */\n clearTarget: function () {\n config.uiRoutes.target = null;\n },\n /**\n * Redirects user to the login page\n */\n openLogin: openLogin,\n /**\n * Redirects user to the home page\n */\n openHome: function () {\n goTo(getHome());\n },\n /**\n * Returns saved current user or load it from backed\n * Always returns {Promise}\n * @returns {Promise}\n */\n getCurrentUser: function () {\n return $rootScope.currentUser ? $q.resolve($rootScope.currentUser) : service.refreshCurrentUser();\n },\n /**\n * Loads user from backed using currentUser endpoint or getUser handler\n * Always returns {Promise}\n * @returns {Promise}\n */\n refreshCurrentUser: function() {\n return config.handlers.getUser().then(function (user) {\n $rootScope.currentUser = user;\n service.openTarget();\n return $rootScope.currentUser;\n })\n },\n /**\n * Returns true if user is authenticated\n * Warning! It does not check backend.\n * @returns {boolean} true if user is authenticated\n */\n isAuthenticated: function () {\n return !!$rootScope.currentUser;\n },\n /**\n * Logs user out from the system and redirects it to the login page\n */\n logout: function () {\n return config.handlers.logout();\n },\n /**\n * Allows you to configure angular-spa-auth module and start the init process.\n * Should be called in the #run method of you module\n * @param {Object} configuration contains all the configs\n * @param {String=} configuration.verbose activates console.info output if true\n * @param {String[]=} configuration.publicUrls list url that are available for unauthorized users\n * @param {Object=} configuration.endpoints gives you ability to setup all the backed endpoints that will own roles in the authentication process\n * @param {Object=} configuration.uiRoutes helps you automatically redirect user to the specified UI routes such as home and login\n * @param {String=} configuration.uiRoutes.home home route\n * @param {String=} configuration.uiRoutes.login login route\n * @param {Object=} configuration.handlers allows you to provide you implementation for key methods of authentication process\n */\n run: function (configuration) {\n if (configuration) {\n if(configuration.publicUrls) {\n //publicUrls should be completely replaced by new value if provided\n //to provide ability to set new array with only one route\n config.publicUrls = configuration.publicUrls;\n }\n\n config = angular.merge(config, configuration);\n }\n init()\n },\n /**\n * Login user using provided credentials\n * @param {Object} credentials object with any type of information that is needed to compelete authentication process\n */\n login: function (credentials) {\n return config.handlers.login(credentials)\n .then(service.refreshCurrentUser)\n .then(config.handlers.success)\n .catch(onError);\n }\n };\n\n return service\n }]);\n})();","/*! http://mths.be/startswith v0.2.0 by @mathias */\nif (!String.prototype.startsWith) {\n\t(function() {\n\t\t'use strict'; // needed to support `apply`/`call` with `undefined`/`null`\n\t\tvar defineProperty = (function() {\n\t\t\t// IE 8 only supports `Object.defineProperty` on DOM elements\n\t\t\ttry {\n\t\t\t\tvar object = {};\n\t\t\t\tvar $defineProperty = Object.defineProperty;\n\t\t\t\tvar result = $defineProperty(object, object, object) && $defineProperty;\n\t\t\t} catch(error) {}\n\t\t\treturn result;\n\t\t}());\n\t\tvar toString = {}.toString;\n\t\tvar startsWith = function(search) {\n\t\t\tif (this == null) {\n\t\t\t\tthrow TypeError();\n\t\t\t}\n\t\t\tvar string = String(this);\n\t\t\tif (search && toString.call(search) == '[object RegExp]') {\n\t\t\t\tthrow TypeError();\n\t\t\t}\n\t\t\tvar stringLength = string.length;\n\t\t\tvar searchString = String(search);\n\t\t\tvar searchLength = searchString.length;\n\t\t\tvar position = arguments.length > 1 ? arguments[1] : undefined;\n\t\t\t// `ToInteger`\n\t\t\tvar pos = position ? Number(position) : 0;\n\t\t\tif (pos != pos) { // better `isNaN`\n\t\t\t\tpos = 0;\n\t\t\t}\n\t\t\tvar start = Math.min(Math.max(pos, 0), stringLength);\n\t\t\t// Avoid the `indexOf` call if no match is possible\n\t\t\tif (searchLength + start > stringLength) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar index = -1;\n\t\t\twhile (++index < searchLength) {\n\t\t\t\tif (string.charCodeAt(start + index) != searchString.charCodeAt(index)) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t};\n\t\tif (defineProperty) {\n\t\t\tdefineProperty(String.prototype, 'startsWith', {\n\t\t\t\t'value': startsWith,\n\t\t\t\t'configurable': true,\n\t\t\t\t'writable': true\n\t\t\t});\n\t\t} else {\n\t\t\tString.prototype.startsWith = startsWith;\n\t\t}\n\t}());\n}\n"]} \ No newline at end of file diff --git a/package.json b/package.json index 0bce27b..a7e91ab 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "angular-spa-auth", "title": "Angular SPA Auth", - "version": "0.7.1", + "version": "1.0.0", "author": "Volodymyr Lavrynovych ", "contributors": [ "Volodymyr Lavrynovych " diff --git a/src/angular-spa-auth.js b/src/angular-spa-auth.js index eded604..3afa505 100644 --- a/src/angular-spa-auth.js +++ b/src/angular-spa-auth.js @@ -49,7 +49,7 @@ return $rootScope.currentUser !== undefined; } }]) - .service('AuthService', ['$rootScope', '$q', '$http', '$location', function ($rootScope, $q, $http, $location) { + .service('AuthService', ['$rootScope', '$q', '$http', '$location', '$route', function ($rootScope, $q, $http, $location, $route) { // ------------------------------------------------------------------------/// Config var config = { @@ -137,7 +137,7 @@ // ------------------------------------------------------------------------/// Private function info(message) { - _log(console.info, message) + _log(console.info, 'AuthService: ' + message) } function error(err) { @@ -151,8 +151,15 @@ } function goTo(route) { + info(($location.path() || 'unknown') + ' -----> ' + route); if(route == config.uiRoutes.login && service.isAuthenticated()) { - $location.path(getHome()); + info('User is already authenticated and cannot open login page: ' + route); + route = getHome(); + $location.path(route); + info('Redirected to the ' + route); + } else if(route == $location.path()) { + info('Reload: ' + route); + $route.reload() } else { $location.path(route); info('Redirected to the ' + route); @@ -173,6 +180,7 @@ } function init() { + info('init'); isAuthenticated() .then(service.refreshCurrentUser) .then(function (user) { @@ -235,6 +243,7 @@ */ openTarget: function () { var target = config.uiRoutes.target || getHome(); + info('Open target: ' + target); goTo(target); service.clearTarget() }, diff --git a/test/test.app.js b/test/test.app.js index e4b9289..159ae22 100644 --- a/test/test.app.js +++ b/test/test.app.js @@ -1,6 +1,6 @@ describe('angular-spa-auth', function () { - var AuthService, $rootScope, $location, $httpBackend; + var AuthService, $rootScope, $location, $httpBackend, isAuthenticatedResponse; beforeEach(module('test-app')); beforeEach(inject(function(_AuthService_, _$rootScope_, _$location_, _$httpBackend_){ @@ -10,9 +10,15 @@ describe('angular-spa-auth', function () { $location = _$location_; $httpBackend = _$httpBackend_; - //setup backend - $httpBackend.whenGET(ENDPOINTS.IS_AUTHENTICATED_SUCCESS).respond(200, true); - $httpBackend.whenGET(ENDPOINTS.IS_AUTHENTICATED_ERROR).respond(200, false); + //setup backend: + // html templates + $httpBackend.whenGET('./private/private.html').respond(200, '
hello
'); + $httpBackend.whenGET('./login/login.html').respond(200, '
hello
'); + $httpBackend.whenGET('./home/home.html').respond(200, '
hello
'); + $httpBackend.whenGET('./public/public.html').respond(200, '
hello
'); + + // rest api + isAuthenticatedResponse = $httpBackend.whenGET(ENDPOINTS.IS_AUTHENTICATED_ERROR).respond(200, false); $httpBackend.whenPOST(ENDPOINTS.LOGIN_SUCCESS).respond(200); $httpBackend.whenPOST(ENDPOINTS.LOGIN_ERROR).respond(400); @@ -24,6 +30,15 @@ describe('angular-spa-auth', function () { $httpBackend.whenGET(ENDPOINTS.CURRENT_USER_ERROR).respond(500); })); + afterEach(inject(function(){ + $location._path && ($location.path = $location._path); + })); + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + describe('test', function () { var events = []; @@ -31,27 +46,21 @@ describe('angular-spa-auth', function () { it('User is not logged in on start', function () { //given: expect($rootScope.currentUser).toBeUndefined(); - events = []; - - $rootScope.$on("$routeChangeStart", function(e) { - e.path = $location.path(); - e.i = events.length; - events.push(e); - }); + watch($location); //when: isAuthenticated method is triggered but we still do not know status of user - changeRoute('/public'); + $location.path('/public'); //then: public page is always available expect($location.path()).toEqual('/public'); expect($rootScope.currentUser).toBeUndefined(); - checkEvent(false); + checkEvent(false, '/public'); //when: isAuthenticated method is triggered but we still do not know status of user - changeRoute('/private'); + $location.path('/private'); //then: private page is rejected - checkEvent(true); + checkEvent(true, '/private'); expect($rootScope.currentUser).toBeUndefined(); //when: trigger after init @@ -62,7 +71,7 @@ describe('angular-spa-auth', function () { expect($rootScope.currentUser).toEqual(false); //when: try to go to the public page - changeRoute('/public'); + $location.path('/public'); //then: successfully opened public page, user is still not authenticated expect($location.path()).toEqual('/public'); @@ -70,13 +79,12 @@ describe('angular-spa-auth', function () { checkEvent(false); //when: try to go to the private page - changeRoute('/private'); - + $location.path('/private'); //then: rejected and redirected back to the login page, not authenticated expect($location.path()).toEqual(AuthService.config.uiRoutes.login); expect($rootScope.currentUser).toEqual(false); - checkEvent(true); + checkEvent(true, '/private'); //when: try to login using some credentials AuthService.login(credentials); @@ -87,20 +95,20 @@ describe('angular-spa-auth', function () { expect($rootScope.currentUser).toEqual(USER); //when: try to go to the private page again - changeRoute('/private'); + $location.path('/private'); //then: successfully loaded private page expect($location.path()).toEqual('/private'); expect($rootScope.currentUser).toEqual(USER); - checkEvent(false); + checkEvent(false, '/private'); //when: try to go to the login page after login - changeRoute(AuthService.config.uiRoutes.login); + $location.path(AuthService.config.uiRoutes.login); //then: should be rejected and redirected to the home page expect($location.path()).toEqual(AuthService.config.uiRoutes.home); expect($rootScope.currentUser).toEqual(USER); - checkEvent(true); + checkEvent(true, AuthService.config.uiRoutes.login); //when: logout AuthService.logout(); @@ -111,29 +119,23 @@ describe('angular-spa-auth', function () { expect($rootScope.currentUser).toBeNull(); //when: try to go to the private page again - changeRoute('/private'); + $location.path('/private'); //then: should be rejected expect($location.path()).toEqual(AuthService.config.uiRoutes.login); expect($rootScope.currentUser).toBeNull(); - checkEvent(true); + checkEvent(true, '/private'); }); - it('Refresh when you are on the login page and login', function () { //given: expect($rootScope.currentUser).toBeUndefined(); - events = []; - AuthService.config.uiRoutes.target = AuthService.config.uiRoutes.login; - $rootScope.$on("$routeChangeStart", function(e) { - e.path = $location.path(); - e.i = events.length; - events.push(e); - }); + AuthService.config.uiRoutes.target = AuthService.config.uiRoutes.login; + watch($location); //when: isAuthenticated method is triggered but we still do not know status of user - changeRoute(AuthService.config.uiRoutes.login); + $location.path(AuthService.config.uiRoutes.login); //then: login page is available expect($location.path()).toEqual(AuthService.config.uiRoutes.login); @@ -155,32 +157,120 @@ describe('angular-spa-auth', function () { expect($location.path()).toEqual(AuthService.config.uiRoutes.home); //when: try again to go to the login page - changeRoute(AuthService.config.uiRoutes.login); + $location.path(AuthService.config.uiRoutes.login); //then: successfully authenticated and redirected to the home page EVEN if the target is login expect($location.path()).toEqual(AuthService.config.uiRoutes.home); }); - function changeRoute(path) { - $location.path(path); - $rootScope.$broadcast("$routeChangeStart", { - $$route: { - originalPath: path - } + it('Refresh when you are authenticated and on the private page', function () { + //setup backend + isAuthenticatedResponse.respond(200, true); + + //given: + // we can only test the second part of refresh when app start after reload + // isAuthenticated returns true, getCurrentUser returns user, the target page is set to the /private path + AuthService.config.uiRoutes.target = '/private'; + $location.path('/private'); + watch($location); + + //and: + expect($location.path()).toEqual('/private'); + expect($rootScope.currentUser).toBeUndefined(); + + //and: trigger after init + $httpBackend.flush(1); + $location._path('/private'); // we still on the private page + $httpBackend.flush(); + + //then: we are on login page and user is set to false, because we checked his authentication status + expect($location.path()).toEqual('/private'); + expect($rootScope.currentUser).toEqual(USER); + }); + + function watch($location) { + events = []; + $rootScope.$on("$routeChangeStart", function(e, next) { + e.path = next.$$route.originalPath; + e.i = events.length; + console.info('<-[ Event ] << Add: ', 'index = ' + e.i, 'event.path = ' + e.path, 'prevented = ' + e.defaultPrevented); + events.push(e); }); - $rootScope.$broadcast("$routeChangeSuccess"); + + $location._path = $location.path; + var inProgress = false; + var queue = []; + + $location.path = function (path) { + if(!path) { + return $location._path() + } + + $location._path(path); + + if(inProgress) { + //add to queue + console.info('[[[ Queue ]]] << Add: ' + path); + queue.push(path); + } else { + inProgress = true; + process(path); + + if(queue.length) { + var next = queue[0]; + process(next); + queue.splice(0, 1); + } + inProgress = false; + } + + function process(path) { + console.info('[[[ Queue ]]] >> Process: ' + path); + console.info('Attempt to go to the: ' + path); + $rootScope.$broadcast("$routeChangeStart", { + $$route: { + originalPath: path + } + }); + + $rootScope.$on("$routeChangeSuccess", function(e) { + inProgress = false; + }); + + if(!getLastEvent().defaultPrevented) { + $rootScope.$broadcast("$routeChangeSuccess"); + } + } + } } /** * Should be called only when $routeChangeStart is triggered * @param prevented + * @param [path] */ - function checkEvent(prevented) { - var event = events[events.length -1]; - console.info('checkEvent', 'index = ' + event.i, 'event.path = ' + event.path); + function checkEvent(prevented, path) { + var event; + if(path) { + var index = events.length - 1; + for ( ; index >= 0; index--) { + if (events[index].path == path) { + event = events[index]; + break; + } + } + } else { + event = getLastEvent(); + } + + console.info('<-[ Event ] >> Check: ', 'index = ' + event.i, 'event.path = ' + event.path, 'prevented = ' + event.defaultPrevented); expect(event).not.toBeNull(); expect(event.defaultPrevented).toEqual(prevented); } + + function getLastEvent() { + return events[events.length -1]; + } }) }); \ No newline at end of file