├── .bowerrc ├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── bower.json ├── dist ├── angular-digest-auth.dev.js ├── angular-digest-auth.js └── angular-digest-auth.min.js ├── karma.conf.js ├── package.json ├── src ├── angular-digest-auth.js ├── config │ ├── config-module.js │ └── config-state-machine.js └── services │ ├── auth-client.js │ ├── auth-identity.js │ ├── auth-requests.js │ ├── auth-server.js │ ├── auth-service.js │ ├── auth-storage.js │ └── dg-auth-service.js └── tests ├── angular-digest-authSpec.js ├── authClientSpec.js ├── authIdentitySpec.js ├── authRequestsSpec.js ├── authServerSpec.js ├── authServiceSpec.js └── authStorageSpec.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "lib", 3 | "json": "bower.json" 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | lib 3 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | 5 | before_install: 6 | - "export DISPLAY=:99.0" 7 | - "sh -e /etc/init.d/xvfb start" -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2014 Matteo Tafani Alunno 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | * this software and associated documentation files (the "Software"), to deal in 8 | * the Software without restriction, including without limitation the rights to 9 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | * the Software, and to permit persons to whom the Software is furnished to do so, 11 | * subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | 'use strict'; 25 | 26 | module.exports = function(grunt) 27 | { 28 | // Project configuration. 29 | grunt.initConfig({ 30 | pkg: grunt.file.readJSON('package.json'), 31 | meta: { 32 | banner: '/**\n' + 33 | ' * <%= pkg.description %>\n' + 34 | ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + 35 | ' * @link <%= pkg.homepage %>\n' + 36 | ' * @author <%= pkg.author %>\n' + 37 | ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + 38 | ' */\n' 39 | }, 40 | dirs: { 41 | src: 'src', 42 | dest: 'dist' 43 | }, 44 | bower: { 45 | install: {} 46 | }, 47 | concat: { 48 | options: { 49 | banner: '<%= meta.banner %>\n\n\'use strict\';\n\n', 50 | process: function(src, filepath) { 51 | return '\n// Source: ' + filepath + '\n\n' + 52 | src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); 53 | } 54 | }, 55 | dist: { 56 | src: [ 57 | 'src/angular-digest-auth.js', 58 | 'src/config/config-module.js', 59 | 'src/config/config-state-machine.js', 60 | 'src/services/dg-auth-service.js', 61 | 'src/services/auth-client.js', 62 | 'src/services/auth-events.js', 63 | 'src/services/auth-identity.js', 64 | 'src/services/auth-server.js', 65 | 'src/services/auth-service.js', 66 | 'src/services/auth-requests.js', 67 | 'src/services/auth-storage.js' 68 | ], 69 | dest: '<%= dirs.dest %>/<%= pkg.name %>.dev.js' 70 | } 71 | }, 72 | removelogging: { 73 | dist: { 74 | src: ['<%= concat.dist.dest %>'], 75 | dest: '<%= dirs.dest %>/<%= pkg.name %>.js' 76 | } 77 | }, 78 | uglify: { 79 | options: { 80 | banner: '<%= meta.banner %>' 81 | }, 82 | dist: { 83 | src: ['<%= removelogging.dist.dest %>'], 84 | dest: '<%= dirs.dest %>/<%= pkg.name %>.min.js' 85 | } 86 | }, 87 | changelog: { 88 | options: { 89 | dest: 'CHANGELOG.md' 90 | } 91 | }, 92 | karma: { 93 | options: { 94 | configFile: 'karma.conf.js' 95 | }, 96 | build: { 97 | singleRun: true, 98 | autoWatch: false 99 | }, 100 | travis: { 101 | singleRun: true, 102 | autoWatch: false, 103 | browsers: ['Firefox'] 104 | } 105 | }, 106 | bump: { 107 | options: { 108 | files: ['package.json', 'bower.json'], 109 | createTag: false, 110 | commit: false, 111 | push: false 112 | } 113 | } 114 | }); 115 | 116 | // Load the plugin that provides the "uglify" task. 117 | grunt.loadNpmTasks('grunt-contrib-uglify'); 118 | 119 | // Load the plugin that provides the "changelog" task. 120 | grunt.loadNpmTasks('grunt-conventional-changelog'); 121 | 122 | // Load the plugin that provides the "bower" task. 123 | grunt.loadNpmTasks('grunt-bower-task'); 124 | 125 | // Load the plugin that provides the "concat" task. 126 | grunt.loadNpmTasks('grunt-contrib-concat'); 127 | 128 | // Load the plugin that provides the "removelogging" task. 129 | grunt.loadNpmTasks('grunt-remove-logging'); 130 | 131 | // Load the plugin that provides the "karma" task. 132 | grunt.loadNpmTasks('grunt-karma'); 133 | 134 | // Load the plugin that provides the "bump" task. 135 | grunt.loadNpmTasks('grunt-bump'); 136 | 137 | // Default task. 138 | grunt.registerTask('default', ['build']); 139 | 140 | // Test task. 141 | grunt.registerTask('test', ['karma:build']); 142 | 143 | // Travis task. 144 | grunt.registerTask('travis', ['bower', 'karma:travis']); 145 | 146 | // Build task. 147 | grunt.registerTask('build', ['test', 'concat', 'removelogging', 'uglify']); 148 | 149 | // Version task. 150 | grunt.registerTask('version', ['bump', 'build']); 151 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Matteo Tafani Alunno 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This library is deprecated. Sorry guys I have no time to maintain it. Check my new project [@layerr](https://github.com/tafax/layerr) 2 | 3 | # AngularJS HTTP Digest Authentication 4 | [![Build Status](https://travis-ci.org/tafax/angular-digest-auth.png?branch=master)](https://travis-ci.org/tafax/angular-digest-auth) 5 | 6 | It is an AngularJS module to manage [HTTP Digest Authentication](http://en.wikipedia.org/wiki/Digest_access_authentication) in the REST API web apps. 7 | It provides functionality to avoid the default form of browsers and use your own. The login and logout are based on digest access authentication 8 | and the module helps to manage the user identity in the client side. 9 | You can use this module in order to protect your app and authorize the user to navigate inside it. 10 | 11 | ## Features 12 | * Using your own login and logout 13 | * Interceptor to pass the authorization in all further requests after the login 14 | * Protection for login with a limitation for the number of requests 15 | * Storage for reusing credentials 16 | * Managing identity with `authIdentity` service 17 | * Authorization checking as promise with `dgAuthService` 18 | * Custom header to parse server information(realm, opaque, domain, etc...) 19 | * Custom callbacks to handle all authorization states(login successful, login error, login required, etc...) 20 | 21 | ## Installation 22 | You can download this by: 23 | * Using bower and running `bower install angular-digest-auth --save` (recommended) 24 | * Downloading manually the [unminified version](https://raw.github.com/tafax/angular-digest-auth/master/dist/angular-digest-auth.js) or 25 | the [minified production version](https://raw.github.com/tafax/angular-digest-auth/master/dist/angular-digest-auth.min.js) 26 | 27 | After installation, import the module in your app. 28 | ````javascript 29 | var app = angular.module('myApp', ['dgAuth']); 30 | ```` 31 | 32 | ## Dependencies 33 | This module depends by [angular](https://github.com/angular/angular.js), [angular-state-machine](https://github.com/tafax/angular-state-machine) 34 | and [angular-md5](https://github.com/gdi2290/angular-md5). 35 | 36 | ## Configuration 37 | You have to provide a few configurations in order to work. 38 | 39 | ### Login and logout 40 | Create the services to sign in and sign out in order to simulate the login and 41 | the logout in your app. `signin` service should return the JSON of the user identity. 42 | You can use the user identity with `authIdentity` service. 43 | ````javascript 44 | app.config(['dgAuthServiceProvider', function(dgAuthServiceProvider) 45 | { 46 | dgAuthServiceProvider.setConfig({ 47 | login: { 48 | method: 'POST', 49 | url: '/signin' 50 | ... 51 | //Other HTTP configurations. 52 | }, 53 | logout: { 54 | method: 'POST', 55 | url: '/signout' 56 | ... 57 | //Other HTTP configurations. 58 | } 59 | }); 60 | }]); 61 | ```` 62 | 63 | ### Header 64 | How to configure the header to parse server information. You should define a custom header in the server side 65 | in order to avoid the browser form and use your custom login form. 66 | ````javascript 67 | app.config(['dgAuthServiceProvider', function(dgAuthServiceProvider) 68 | { 69 | /** 70 | * Specifies the header to look for server information. 71 | * The header you have used in the server side. 72 | */ 73 | dgAuthServiceProvider.setHeader('Your-Header-For-Authentication'); 74 | }]); 75 | ```` 76 | 77 | ### Limit 78 | How to configure the limit of number requests to sign in. When the limit is exceeded 79 | `limit` of login callbacks is invoked. The default limit is 4. 80 | N.B.: the limit includes the request to sign in place during the invocation of the `start` method. 81 | ````javascript 82 | app.config(['dgAuthServiceProvider', function(dgAuthServiceProvider) 83 | { 84 | /** 85 | * Sets the limit to 5 requests. 86 | * 4 requests after the invocation of start method. 87 | */ 88 | dgAuthServiceProvider.setLimit(5); 89 | 90 | /** 91 | * Sets the limit of requests to infinite. 92 | */ 93 | dgAuthServiceProvider.setLimit('inf'); 94 | }]); 95 | ```` 96 | 97 | ### Callbacks 98 | How to configure what happens at the user login and/or logout. 99 | ````javascript 100 | app.config(['dgAuthServiceProvider', function(dgAuthServiceProvider) 101 | { 102 | /** 103 | * You can add the callbacks to manage what happens after 104 | * successful of the login. 105 | */ 106 | dgAuthServiceProvider.callbacks.login.push(['serviceInject', function(serviceInject) 107 | { 108 | return { 109 | successful: function(response) 110 | { 111 | //Your code... 112 | }, 113 | error: function(response) 114 | { 115 | //Your code... 116 | }, 117 | required: function(response) 118 | { 119 | //Your code... 120 | }, 121 | limit: function(response) 122 | { 123 | //Your code... 124 | } 125 | }; 126 | }]); 127 | 128 | //This is the same for the logout. 129 | 130 | /** 131 | * You can add the callbacks to manage what happens after 132 | * successful of the logout. 133 | */ 134 | dgAuthServiceProvider.callbacks.logout.push(['serviceInject', function(serviceInject) 135 | { 136 | return { 137 | successful: function(response) 138 | { 139 | //Your code... 140 | }, 141 | error: function(response) 142 | { 143 | //Your code... 144 | } 145 | }; 146 | }]); 147 | }]); 148 | ```` 149 | 150 | ### Storage 151 | By default, after the user has made the login, the credentials are stored in `sessionStorage` and the module 152 | processes all further requests with this credentials. If you want to restore the user credentials when 153 | he returns in your app, you can specify the `localStorage` as default storage. 154 | ````javascript 155 | app.config(['dgAuthServiceProvider', function(dgAuthServiceProvider) 156 | { 157 | /** 158 | * Uses localStorage instead the sessionStorage. 159 | */ 160 | dgAuthServiceProvider.setStorage(localStorage); 161 | }]); 162 | ```` 163 | 164 | Obviously, if you want to specify your own storage object, you can :). 165 | 166 | ## Usage 167 | For basic usage, you can launch the `start()` when your app goes run. 168 | ````javascript 169 | app.run(['dgAuthService', function(dgAuthService) 170 | { 171 | /** 172 | * It tries to sign in. If the service doesn't find 173 | * the credentials stored or the user is not signed in yet, 174 | * the service executes the required function. 175 | */ 176 | dgAuthService.start(); 177 | }]); 178 | ```` 179 | 180 | In your login controller you should provide the credentials submitted by user. 181 | Then you have to sign in another time. 182 | ````javascript 183 | $scope.submit = function(user) 184 | { 185 | dgAuthService.setCredentials(user.username, user.password); 186 | dgAuthService.signin(); 187 | }; 188 | ```` 189 | 190 | If the login is successful, all further requests, for the API in the domain specified by the server header, 191 | contains the authentication to authorize the user. 192 | 193 | ## Authorization 194 | You can use a functionality of `dgAuthService` to authorize the user to navigate in your app. 195 | ````javascript 196 | app.config(['$routeProvider', function($routeProvider) 197 | { 198 | /** 199 | * Use a variable in resolve to authorize the users. 200 | * The method 'isAuthorized()' returns a promise 201 | * which you can use to authorize the requests. 202 | */ 203 | $routeProvider.when('some/path', { 204 | ... 205 | resolve: { 206 | auth: ['dgAuthService', '$q', '$location', function(dgAuthService, $q, $location) 207 | { 208 | var deferred = $q.defer(); 209 | 210 | dgAuthService.isAuthorized().then(function(authorized) 211 | { 212 | if(authorized) 213 | deferred.resolve(); 214 | else 215 | deferred.reject(); 216 | }, 217 | function(authorized) 218 | { 219 | deferred.reject(); 220 | }); 221 | 222 | return deferred.promise; 223 | }] 224 | } 225 | }); 226 | }]); 227 | ```` 228 | 229 | ## License 230 | [MIT](https://github.com/tafax/angular-digest-auth/blob/master/LICENSE) 231 | 232 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-digest-auth", 3 | "version": "0.4.3", 4 | "main": "./dist/angular-digest-auth.min.js", 5 | "description": "AngularJS module to manage HTTP Digest Authentication", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/tafax/angular-digest-auth.git" 9 | }, 10 | "dependencies": { 11 | "angular-cookies": "*", 12 | "angular-md5": "*", 13 | "angular-state-machine": "*", 14 | "angular": "*" 15 | }, 16 | "devDependencies": { 17 | "angular-mocks": "*" 18 | }, 19 | "ignore": [ 20 | "node_modules", 21 | "components", 22 | "lib" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /dist/angular-digest-auth.dev.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AngularJS module to manage HTTP Digest Authentication 3 | * @version v0.4.3 - 2014-02-02 4 | * @link https://github.com/tafax/angular-digest-auth 5 | * @author Matteo Tafani Alunno 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | 9 | 10 | 'use strict'; 11 | 12 | 13 | // Source: src/angular-digest-auth.js 14 | 15 | /** 16 | * dgAuth provides functionality to manage 17 | * user authentication 18 | */ 19 | var dgAuth = angular.module('dgAuth', ['angular-md5', 'FSM']); 20 | 21 | // Source: src/config/config-module.js 22 | 23 | /** 24 | * Configures http to intercept requests and responses with error 401. 25 | */ 26 | dgAuth.config(['$httpProvider', function($httpProvider) 27 | { 28 | $httpProvider.interceptors.push([ 29 | '$q', 30 | 'authService', 31 | 'authClient', 32 | 'authServer', 33 | 'stateMachine', 34 | function($q, authService, authClient, authServer, stateMachine) 35 | { 36 | return { 37 | 'request': function(request) 38 | { 39 | var login = authService.getCredentials(); 40 | var header = authClient.processRequest(login.username, login.password, request.method, request.url); 41 | 42 | if(header) 43 | request.headers['Authorization'] = header; 44 | 45 | return (request || $q.when(request)); 46 | }, 47 | 'responseError': function(rejection) 48 | { 49 | if(rejection.status === 401) 50 | { 51 | if(!authServer.parseHeader(rejection)) 52 | { 53 | return $q.reject(rejection); 54 | } 55 | 56 | var deferred = $q.defer(); 57 | 58 | authService.setRequest(rejection.config, deferred); 59 | stateMachine.send('401', {response: rejection}); 60 | 61 | return deferred.promise; 62 | } 63 | 64 | return $q.reject(rejection); 65 | } 66 | }; 67 | }]); 68 | }]); 69 | 70 | // Source: src/config/config-state-machine.js 71 | 72 | dgAuth.config(['stateMachineProvider', function(stateMachineProvider) 73 | { 74 | stateMachineProvider.config({ 75 | init: { 76 | transitions: { 77 | run: 'restoringCredentials' 78 | } 79 | }, 80 | restoringCredentials: { 81 | transitions: { 82 | restored: 'settingCredentials' 83 | }, 84 | //Restores the credentials and propagate 85 | action: ['authStorage', 'params', function(authStorage, params) 86 | { 87 | if(authStorage.hasCredentials()) 88 | { 89 | params.credentials = { 90 | username: authStorage.getUsername(), 91 | password: authStorage.getPassword() 92 | }; 93 | } 94 | 95 | return params; 96 | }] 97 | }, 98 | settingCredentials: { 99 | transitions: { 100 | signin: 'loginRequest' 101 | }, 102 | //Sets the credentials as candidate 103 | action: ['authService', 'params', function(authService, params) 104 | { 105 | if(params.hasOwnProperty('credentials')) 106 | { 107 | var credentials = params.credentials; 108 | authService.setCredentials(credentials.username, credentials.password); 109 | } 110 | }] 111 | }, 112 | loginRequest: { 113 | transitions: { 114 | //Checks if the credentials are present(loginError) or not(waitingCredentials) 115 | 401: [ 116 | { 117 | to: 'waitingCredentials', 118 | predicate: ['authService', 'authRequests', function(authService, authRequests) 119 | { 120 | return (!authService.hasCredentials() && authRequests.getValid()); 121 | }] 122 | }, 123 | { 124 | to: 'loginError', 125 | predicate: ['authService', 'authRequests', function(authService, authRequests) 126 | { 127 | return (authService.hasCredentials() && authRequests.getValid()); 128 | }] 129 | }, 130 | { 131 | to: 'failureLogin', 132 | predicate: ['authRequests', function(authRequests) 133 | { 134 | return !authRequests.getValid(); 135 | }] 136 | }], 137 | 201: 'loggedIn' 138 | }, 139 | //Does the request to the server and save the promise 140 | action: ['authRequests', function(authRequests) 141 | { 142 | authRequests.signin(); 143 | }] 144 | }, 145 | loginError: { 146 | transitions: { 147 | submitted: 'settingCredentials' 148 | }, 149 | //Delete the credentials that are invalid and notify the error 150 | action: ['authService', 'params', function(authService, params) 151 | { 152 | authService.clearCredentials(); 153 | var callbacks = authService.getCallbacks('login.error'); 154 | for(var i in callbacks) 155 | { 156 | var callback = callbacks[i]; 157 | callback(params.response); 158 | } 159 | }] 160 | }, 161 | waitingCredentials: { 162 | transitions: { 163 | submitted: 'settingCredentials' 164 | }, 165 | //Checks the previous state and notify the credential need 166 | action: [ 167 | 'authService', 168 | 'authIdentity', 169 | 'authStorage', 170 | 'name', 171 | 'params', 172 | function(authService, authIdentity, authStorage, name, params) 173 | { 174 | if(name == 'logoutRequest') 175 | { 176 | authIdentity.clear(); 177 | authService.clearRequest(); 178 | authService.clearCredentials(); 179 | authStorage.clearCredentials(); 180 | 181 | var callbacksLogout = authService.getCallbacks('logout.successful'); 182 | for(var i in callbacksLogout) 183 | { 184 | var funcSuccessful = callbacksLogout[i]; 185 | funcSuccessful(params.response); 186 | } 187 | } 188 | 189 | authIdentity.suspend(); 190 | authService.clearCredentials(); 191 | authStorage.clearCredentials(); 192 | var callbacksLogin = authService.getCallbacks('login.required'); 193 | for(var j in callbacksLogin) 194 | { 195 | var funcRequest = callbacksLogin[j]; 196 | funcRequest(params.response); 197 | } 198 | }] 199 | }, 200 | loggedIn: { 201 | transitions: { 202 | signout: 'logoutRequest', 203 | 401: 'waitingCredentials' 204 | }, 205 | //Checks the previous state and creates the identity and notify the login successful 206 | action: [ 207 | 'authService', 208 | 'authIdentity', 209 | 'authStorage', 210 | 'name', 211 | 'params', 212 | function(authService, authIdentity, authStorage, name, params) 213 | { 214 | if(name == 'logoutRequest') 215 | { 216 | var callbacksLogout = authService.getCallbacks('logout.error'); 217 | for(var i in callbacksLogout) 218 | { 219 | var funcError = callbacksLogout[i]; 220 | funcError(params.response); 221 | } 222 | } 223 | 224 | if(name == 'loginRequest') 225 | { 226 | if(authIdentity.isSuspended()) 227 | authIdentity.restore(); 228 | 229 | if(!authIdentity.has()) 230 | authIdentity.set(null, params.response.data); 231 | 232 | authService.clearRequest(); 233 | 234 | var credentials = authService.getCredentials(); 235 | authStorage.setCredentials(credentials.username, credentials.password); 236 | 237 | var callbacksLogin = authService.getCallbacks('login.successful'); 238 | for(var j in callbacksLogin) 239 | { 240 | var funcSuccessful = callbacksLogin[j]; 241 | funcSuccessful(params.response); 242 | } 243 | } 244 | }] 245 | }, 246 | logoutRequest: { 247 | transitions: { 248 | 401: 'loggedIn', 249 | 201: 'waitingCredentials' 250 | }, 251 | //Does the request to the server and save the promise 252 | action: ['authRequests', function(authRequests) 253 | { 254 | authRequests.signout(); 255 | }] 256 | }, 257 | failureLogin: { 258 | action: [ 259 | 'authService', 260 | 'authIdentity', 261 | 'params', 262 | function(authService, authIdentity, params) 263 | { 264 | authIdentity.clear(); 265 | authService.clearCredentials(); 266 | 267 | var callbacksLogin = authService.getCallbacks('login.limit'); 268 | for(var j in callbacksLogin) 269 | { 270 | var funcLimit = callbacksLogin[j]; 271 | funcLimit(params.response); 272 | } 273 | }] 274 | } 275 | }); 276 | }]); 277 | 278 | // Source: src/services/dg-auth-service.js 279 | 280 | dgAuth.provider('dgAuthService', function DgAuthServiceProvider() 281 | { 282 | /** 283 | * Class to provide the API to manage 284 | * the module functionality. 285 | * 286 | * @param {Object} $q 287 | * @param {Object} authIdentity 288 | * @param {Object} authRequests 289 | * @param {StateMachine} stateMachine 290 | * @constructor 291 | */ 292 | function DgAuthService($q, authIdentity, authRequests, stateMachine) 293 | { 294 | /** 295 | * Specifies if the service is started. 296 | * 297 | * @type {boolean} 298 | * @private 299 | */ 300 | var _started = false; 301 | 302 | /** 303 | * Starts the service. 304 | */ 305 | this.start = function() 306 | { 307 | stateMachine.initialize(); 308 | 309 | stateMachine.send('run'); 310 | stateMachine.send('restored'); 311 | stateMachine.send('signin'); 312 | 313 | _started = true; 314 | }; 315 | 316 | /** 317 | * Sends a signin message to the state machine. 318 | */ 319 | this.signin = function() 320 | { 321 | if(!_started) 322 | throw 'You have to start te service first'; 323 | 324 | stateMachine.send('signin'); 325 | }; 326 | 327 | /** 328 | * Sends a signout message to the state machine. 329 | */ 330 | this.signout = function() 331 | { 332 | if(!_started) 333 | throw 'You have to start te service first'; 334 | 335 | stateMachine.send('signout'); 336 | }; 337 | 338 | /** 339 | * Sends a submitted message to the state machine 340 | * with the credentials specified. 341 | * 342 | * @param {string} username 343 | * @param {string} password 344 | */ 345 | this.setCredentials = function(username, password) 346 | { 347 | if(!_started) 348 | throw 'You have to start te service first'; 349 | 350 | stateMachine.send('submitted', { 351 | credentials: { 352 | username: username, 353 | password: password 354 | } 355 | }); 356 | }; 357 | 358 | /** 359 | * Checks the authentication. 360 | * 361 | * @returns {promise|false} 362 | */ 363 | this.isAuthorized = function() 364 | { 365 | var deferred = $q.defer(); 366 | 367 | authRequests.getPromise().then(function() 368 | { 369 | deferred.resolve(authIdentity.has()); 370 | }, 371 | function() 372 | { 373 | deferred.reject(authIdentity.has()) 374 | }); 375 | 376 | return deferred.promise; 377 | }; 378 | } 379 | 380 | /** 381 | * Default storage for user credentials. 382 | * 383 | * @type {Storage} 384 | * @private 385 | */ 386 | var _storage = window.sessionStorage; 387 | 388 | /** 389 | * Sets storage for user credentials. 390 | * 391 | * @param storage 392 | */ 393 | this.setStorage = function(storage) 394 | { 395 | _storage = storage; 396 | }; 397 | 398 | /** 399 | * Gets storage for user credentials. 400 | * 401 | * @returns {Storage} 402 | */ 403 | this.getStorage = function() 404 | { 405 | return _storage; 406 | }; 407 | 408 | /** 409 | * The configuration for the login and logout. 410 | * 411 | * @type {Object} 412 | * @private 413 | */ 414 | var _config = { 415 | login: { 416 | method: 'POST', 417 | url: '/signin' 418 | }, 419 | logout: { 420 | method: 'POST', 421 | url: '/signout' 422 | } 423 | }; 424 | 425 | /** 426 | * Sets the configuration for the requests. 427 | * 428 | * @param {Object} config 429 | */ 430 | this.setConfig = function(config) 431 | { 432 | angular.extend(_config, config); 433 | }; 434 | 435 | /** 436 | * Gets the configuration for the requests. 437 | * 438 | * @returns {Object} 439 | */ 440 | this.getConfig = function() 441 | { 442 | return _config; 443 | }; 444 | 445 | /** 446 | * 447 | * @type {number|string} 448 | * @private 449 | */ 450 | var _limit = 4; 451 | 452 | /** 453 | * Sets the limit for the login requests number. 454 | * 455 | * @param {number|string} limit 456 | */ 457 | this.setLimit = function(limit) 458 | { 459 | _limit = limit; 460 | }; 461 | 462 | /** 463 | * Gets the limit for the login requests number. 464 | * 465 | * @returns {number|string} 466 | */ 467 | this.getLimit = function() 468 | { 469 | return _limit; 470 | }; 471 | 472 | /** 473 | * Callbacks configuration. 474 | * 475 | * @type {{login: Array, logout: Array}} 476 | */ 477 | this.callbacks = { 478 | login: [], 479 | logout: [] 480 | }; 481 | 482 | /** 483 | * The header string. 484 | * 485 | * @type {string} 486 | */ 487 | var _header = ''; 488 | 489 | /** 490 | * Sets the header. 491 | * 492 | * @param {String} header 493 | */ 494 | this.setHeader = function(header) 495 | { 496 | _header = header; 497 | }; 498 | 499 | /** 500 | * Gets the header. 501 | * 502 | * @returns {string} 503 | */ 504 | this.getHeader = function() 505 | { 506 | return _header; 507 | }; 508 | 509 | /** 510 | * Gets a new instance of the service. 511 | * 512 | * @type {*[]} 513 | */ 514 | this.$get = ['$q', 'authIdentity', 'authRequests', 'stateMachine', function($q, authIdentity, authRequests, stateMachine) 515 | { 516 | return new DgAuthService($q, authIdentity, authRequests, stateMachine); 517 | }]; 518 | }); 519 | 520 | // Source: src/services/auth-client.js 521 | 522 | /** 523 | * Manages authentication info in the client scope. 524 | */ 525 | dgAuth.factory('authClient', [ 526 | 'authServer', 527 | 'md5', 528 | function(authServer, md5) 529 | { 530 | /** 531 | * Creates the service to use information generating 532 | * header for each request. 533 | * 534 | * @constructor 535 | */ 536 | function AuthClient() 537 | { 538 | /** 539 | * Chars to select when creating nonce. 540 | * 541 | * @type {string} 542 | * @private 543 | */ 544 | var _chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 545 | 546 | /** 547 | * Current counter. 548 | * 549 | * @type {number} 550 | * @private 551 | */ 552 | var _nc = 0; 553 | 554 | /** 555 | * Generates the cnonce with the given length. 556 | * 557 | * @param length Length of the cnonce. 558 | * @returns {string} 559 | */ 560 | var generateNonce = function(length) 561 | { 562 | var nonce = []; 563 | var charsLength = _chars.length; 564 | 565 | for (var i = 0; i < length; ++i) 566 | { 567 | nonce.push(_chars[Math.random() * charsLength | 0]); 568 | } 569 | 570 | return nonce.join(''); 571 | }; 572 | 573 | /** 574 | * Generate the nc progressively for each request. 575 | * 576 | * @returns {string} 577 | */ 578 | var getNc = function() 579 | { 580 | _nc++; 581 | 582 | var zeros = 8 - _nc.toString().length; 583 | 584 | var nc = ""; 585 | for(var i=0; i= 0) 666 | header = generateHeader(username, password, method, url); 667 | } 668 | 669 | return header; 670 | }; 671 | } 672 | 673 | return new AuthClient(); 674 | }]); 675 | 676 | 677 | // Source: src/services/auth-identity.js 678 | 679 | dgAuth.factory('authIdentity', function() 680 | { 681 | function AuthIdentity() 682 | { 683 | /** 684 | * The current identity of user. 685 | * 686 | * @type {Object|null} 687 | * @private 688 | */ 689 | var _identity = null; 690 | 691 | /** 692 | * Specifies if the identity is suspended. 693 | * 694 | * @type {boolean} 695 | * @private 696 | */ 697 | var _suspended = false; 698 | 699 | /** 700 | * Sets the entire identity fields or 701 | * if key is specified, one of these. 702 | * 703 | * @param {string} [key] 704 | * @param {Object|string|Array} value 705 | */ 706 | this.set = function(key, value) 707 | { 708 | if(_suspended) 709 | return; 710 | 711 | if(key) 712 | { 713 | if(null == _identity) 714 | _identity = {}; 715 | 716 | _identity[key] = value; 717 | } 718 | else 719 | { 720 | if(value instanceof Object) 721 | _identity = value; 722 | else 723 | throw 'You have to provide an object if you want to set the identity without a key.'; 724 | } 725 | }; 726 | 727 | /** 728 | * Gets the entire identity of 729 | * if key is specified, one single field. 730 | * 731 | * @param {string} [key] 732 | * @returns {Object|Array|string|null} 733 | */ 734 | this.get = function(key) 735 | { 736 | if(_suspended) 737 | return null; 738 | 739 | if(!key) 740 | return _identity; 741 | 742 | if(!_identity || !_identity.hasOwnProperty(key)) 743 | return null; 744 | 745 | return _identity[key]; 746 | }; 747 | 748 | /** 749 | * Returns true if the identity 750 | * is properly set. 751 | * 752 | * @returns {boolean} 753 | */ 754 | this.has = function() 755 | { 756 | if(_suspended) 757 | return false; 758 | 759 | return (null !== _identity); 760 | }; 761 | 762 | /** 763 | * Clears the identity. 764 | */ 765 | this.clear = function() 766 | { 767 | _identity = null; 768 | }; 769 | 770 | /** 771 | * Suspends the identity. 772 | */ 773 | this.suspend = function() 774 | { 775 | _suspended = true; 776 | }; 777 | 778 | /** 779 | * Restores identity that is 780 | * previously suspended. 781 | */ 782 | this.restore = function() 783 | { 784 | _suspended = false; 785 | }; 786 | 787 | /** 788 | * Checks if the identity is suspended. 789 | * 790 | * @returns {boolean} 791 | */ 792 | this.isSuspended = function() 793 | { 794 | return _suspended; 795 | }; 796 | } 797 | 798 | return new AuthIdentity(); 799 | }); 800 | 801 | // Source: src/services/auth-server.js 802 | 803 | /** 804 | * Parses and provides server information for the authentication. 805 | */ 806 | dgAuth.provider('authServer', ['dgAuthServiceProvider', function AuthServerProvider(dgAuthServiceProvider) 807 | { 808 | /** 809 | * Creates the service for the server info. 810 | * 811 | * @constructor 812 | */ 813 | function AuthServer(header, authStorage) 814 | { 815 | /** 816 | * The header string. 817 | * 818 | * @type {string} 819 | */ 820 | var _header = header; 821 | 822 | /** 823 | * The regular expression to evaluate server information. 824 | * 825 | * @type {RegExp} 826 | * @private 827 | */ 828 | var _valuePattern = /([a-zA-Z]+)=\"?([a-zA-Z0-9\/\s]+)\"?/; 829 | 830 | /** 831 | * True if the header was correctly parsed. 832 | * 833 | * @type {boolean} 834 | * @private 835 | */ 836 | var _configured = false; 837 | 838 | /** 839 | * The configuration of server information. 840 | * 841 | * @type {{realm: string, domain: string, nonce: string, opaque: string, algorithm: string, qop: string}} 842 | */ 843 | this.info = { 844 | realm: '', 845 | domain: '', 846 | nonce: '', 847 | opaque: '', 848 | algorithm: '', 849 | qop: '' 850 | }; 851 | 852 | /** 853 | * Checks if the header was correctly parsed. 854 | * 855 | * @returns {boolean} 856 | */ 857 | this.isConfigured = function() 858 | { 859 | return _configured; 860 | }; 861 | 862 | /** 863 | * Sets the configuration manually. 864 | * 865 | * @param {Object} server The server information. 866 | */ 867 | this.setConfig = function(server) 868 | { 869 | angular.extend(this.info, server); 870 | 871 | _configured = true; 872 | }; 873 | 874 | /** 875 | * Parses header to set the information. 876 | * 877 | * @param {Object} response The response to login request. 878 | */ 879 | this.parseHeader = function(response) 880 | { 881 | var header = response.headers(_header); 882 | 883 | _configured = false; 884 | 885 | if(null !== header) 886 | { 887 | var splitting = header.split(', '); 888 | 889 | for(var i=0; i 2 || split.length == 0) 1037 | throw 'The type for the callbacks is invalid.'; 1038 | 1039 | var family = split[0]; 1040 | var type = (split.length == 2) ? split[1] : null; 1041 | 1042 | var result = []; 1043 | 1044 | if(callbacks.hasOwnProperty(family)) 1045 | { 1046 | var typedCallbacks = callbacks[family]; 1047 | for(var i in typedCallbacks) 1048 | { 1049 | var func = $injector.invoke(typedCallbacks[i]); 1050 | 1051 | if(type) 1052 | { 1053 | if(func.hasOwnProperty(type)) 1054 | result.push(func[type]); 1055 | } 1056 | else 1057 | result.push(func); 1058 | } 1059 | } 1060 | 1061 | return result; 1062 | }; 1063 | } 1064 | 1065 | /** 1066 | * Gets a new instance of AuthService. 1067 | * 1068 | * @type {Array} 1069 | */ 1070 | this.$get = [ 1071 | '$injector', 1072 | /** 1073 | * Gets a new instance of AuthService. 1074 | * 1075 | * @param {Object} $injector 1076 | * @returns {AuthService} 1077 | */ 1078 | function($injector) 1079 | { 1080 | return new AuthService(dgAuthServiceProvider.callbacks, $injector); 1081 | }]; 1082 | 1083 | }]); 1084 | 1085 | // Source: src/services/auth-requests.js 1086 | 1087 | dgAuth.provider('authRequests', ['dgAuthServiceProvider', function AuthRequestsProvider(dgAuthServiceProvider) 1088 | { 1089 | function AuthRequest(limit, config, $http, authService, stateMachine) 1090 | { 1091 | /** 1092 | * 1093 | * 1094 | * @type {promise|null} 1095 | * @private 1096 | */ 1097 | var _promise = null; 1098 | 1099 | /** 1100 | * 1101 | * 1102 | * @returns {promise|null} 1103 | */ 1104 | this.getPromise = function() 1105 | { 1106 | return _promise; 1107 | }; 1108 | 1109 | /** 1110 | * 1111 | * @type {number} 1112 | * @private 1113 | */ 1114 | var _times = 0; 1115 | 1116 | /** 1117 | * 1118 | * @returns {boolean} 1119 | */ 1120 | this.getValid = function() 1121 | { 1122 | if('inf' == limit) 1123 | return true; 1124 | 1125 | return (_times <= limit); 1126 | }; 1127 | 1128 | var request = function() 1129 | { 1130 | var promise = null; 1131 | 1132 | if(authService.hasRequest()) 1133 | { 1134 | var request = authService.getRequest(); 1135 | promise = $http(request.config).then(function(response) 1136 | { 1137 | request.deferred.resolve(response); 1138 | 1139 | if(_times > 0) 1140 | _times = 0; 1141 | 1142 | if(stateMachine.isAvailable('201')) 1143 | stateMachine.send('201', {response: response}); 1144 | 1145 | return response; 1146 | }, 1147 | function(response) 1148 | { 1149 | request.deferred.reject(response); 1150 | 1151 | if(_times > 0) 1152 | _times = 0; 1153 | 1154 | if(stateMachine.isAvailable('failure')) 1155 | stateMachine.send('failure', {response: response}); 1156 | 1157 | return response; 1158 | }); 1159 | } 1160 | 1161 | return promise; 1162 | }; 1163 | 1164 | /** 1165 | * 1166 | * @returns {promise} 1167 | */ 1168 | this.signin = function() 1169 | { 1170 | _times++; 1171 | 1172 | _promise = request(); 1173 | if(_promise) 1174 | return _promise; 1175 | 1176 | _promise = $http(config.login).then(function(response) 1177 | { 1178 | _times = 0; 1179 | stateMachine.send('201', {response: response}); 1180 | 1181 | return response; 1182 | }, 1183 | function(response) 1184 | { 1185 | _times = 0; 1186 | stateMachine.send('failure', {response: response}); 1187 | 1188 | return response; 1189 | }); 1190 | 1191 | return _promise; 1192 | }; 1193 | 1194 | /** 1195 | * 1196 | * @returns {promise} 1197 | */ 1198 | this.signout = function() 1199 | { 1200 | _promise = request(); 1201 | if(_promise) 1202 | return _promise; 1203 | 1204 | _promise = $http(config.logout).then(function(response) 1205 | { 1206 | stateMachine.send('201', {response: response}); 1207 | 1208 | return response; 1209 | }, 1210 | function(response) 1211 | { 1212 | return response; 1213 | }); 1214 | return _promise; 1215 | }; 1216 | } 1217 | 1218 | this.$get = ['$http', 'authService', 'stateMachine', function($http, authService, stateMachine) 1219 | { 1220 | return new AuthRequest(dgAuthServiceProvider.getLimit(), dgAuthServiceProvider.getConfig(), $http, authService, stateMachine); 1221 | }]; 1222 | }]); 1223 | 1224 | // Source: src/services/auth-storage.js 1225 | 1226 | /** 1227 | * Stores information to remember user credentials 1228 | * and server information. 1229 | */ 1230 | dgAuth.provider('authStorage', ['dgAuthServiceProvider', function AuthStorageProvider(dgAuthServiceProvider) 1231 | { 1232 | /** 1233 | * Creates the service for the storage. 1234 | * You can choose the type of storage to 1235 | * save user credential. 1236 | * Server info are always stored in the 1237 | * session. 1238 | * 1239 | * @param {Storage} storage Storage to save user credentials. 1240 | * @constructor 1241 | */ 1242 | function AuthStorage(storage) 1243 | { 1244 | /** 1245 | * The storage for credentials. 1246 | * 1247 | * @type {Storage} 1248 | * @private 1249 | */ 1250 | var _storage = storage; 1251 | 1252 | /** 1253 | * The session storage. 1254 | * 1255 | * @type {Storage} 1256 | * @private 1257 | */ 1258 | var _sessionStorage = window.sessionStorage; 1259 | 1260 | /** 1261 | * Checks if the storage has some credentials. 1262 | * 1263 | * @returns {boolean} 1264 | */ 1265 | this.hasCredentials = function() 1266 | { 1267 | var username = _storage.getItem('username'); 1268 | var password = _storage.getItem('password'); 1269 | 1270 | return ((null !== username && null !== password) && (undefined !== username && undefined !== password)); 1271 | }; 1272 | 1273 | /** 1274 | * Sets the credentials. 1275 | * 1276 | * @param {String} username 1277 | * @param {String} password 1278 | */ 1279 | this.setCredentials = function(username, password) 1280 | { 1281 | _storage.setItem('username', username); 1282 | _storage.setItem('password', password); 1283 | }; 1284 | 1285 | /** 1286 | * Removes the credentials in the storage. 1287 | */ 1288 | this.clearCredentials = function() 1289 | { 1290 | _storage.removeItem('username'); 1291 | _storage.removeItem('password'); 1292 | }; 1293 | 1294 | /** 1295 | * Checks if storage contains the server information. 1296 | * 1297 | * @returns {boolean} 1298 | */ 1299 | this.hasServerAuth = function() 1300 | { 1301 | var value = _sessionStorage.getItem('server'); 1302 | return (null !== value && undefined !== value); 1303 | }; 1304 | 1305 | /** 1306 | * Sets the server information. 1307 | * 1308 | * @param {Object} server 1309 | */ 1310 | this.setServerAuth = function(server) 1311 | { 1312 | _sessionStorage.setItem('server', angular.toJson(server)); 1313 | }; 1314 | 1315 | /** 1316 | * Gets the server information. 1317 | * 1318 | * @returns {Object} 1319 | */ 1320 | this.getServerAuth = function() 1321 | { 1322 | return angular.fromJson(_sessionStorage.getItem('server')); 1323 | }; 1324 | 1325 | /** 1326 | * Gets the username saved in the storage. 1327 | * 1328 | * @returns {String} 1329 | */ 1330 | this.getUsername = function() 1331 | { 1332 | return _storage.getItem('username'); 1333 | }; 1334 | 1335 | /** 1336 | * Gets the password saved in the storage. 1337 | * 1338 | * @returns {String} 1339 | */ 1340 | this.getPassword = function() 1341 | { 1342 | return _storage.getItem('password'); 1343 | }; 1344 | 1345 | /** 1346 | * Clears the storage. 1347 | */ 1348 | this.clear = function() 1349 | { 1350 | _storage.clear(); 1351 | _sessionStorage.clear(); 1352 | }; 1353 | } 1354 | 1355 | /** 1356 | * Gets a new instance of AuthStorage. 1357 | * 1358 | * @returns {AuthStorageProvider.AuthStorage} 1359 | */ 1360 | this.$get = function() 1361 | { 1362 | return new AuthStorage(dgAuthServiceProvider.getStorage()); 1363 | }; 1364 | }]); 1365 | -------------------------------------------------------------------------------- /dist/angular-digest-auth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AngularJS module to manage HTTP Digest Authentication 3 | * @version v0.4.3 - 2014-02-02 4 | * @link https://github.com/tafax/angular-digest-auth 5 | * @author Matteo Tafani Alunno 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | 9 | 10 | 'use strict'; 11 | 12 | 13 | // Source: src/angular-digest-auth.js 14 | 15 | /** 16 | * dgAuth provides functionality to manage 17 | * user authentication 18 | */ 19 | var dgAuth = angular.module('dgAuth', ['angular-md5', 'FSM']); 20 | 21 | // Source: src/config/config-module.js 22 | 23 | /** 24 | * Configures http to intercept requests and responses with error 401. 25 | */ 26 | dgAuth.config(['$httpProvider', function($httpProvider) 27 | { 28 | $httpProvider.interceptors.push([ 29 | '$q', 30 | 'authService', 31 | 'authClient', 32 | 'authServer', 33 | 'stateMachine', 34 | function($q, authService, authClient, authServer, stateMachine) 35 | { 36 | return { 37 | 'request': function(request) 38 | { 39 | var login = authService.getCredentials(); 40 | var header = authClient.processRequest(login.username, login.password, request.method, request.url); 41 | 42 | if(header) 43 | request.headers['Authorization'] = header; 44 | 45 | return (request || $q.when(request)); 46 | }, 47 | 'responseError': function(rejection) 48 | { 49 | if(rejection.status === 401) 50 | { 51 | if(!authServer.parseHeader(rejection)) 52 | { 53 | return $q.reject(rejection); 54 | } 55 | 56 | var deferred = $q.defer(); 57 | 58 | authService.setRequest(rejection.config, deferred); 59 | stateMachine.send('401', {response: rejection}); 60 | 61 | return deferred.promise; 62 | } 63 | 64 | return $q.reject(rejection); 65 | } 66 | }; 67 | }]); 68 | }]); 69 | 70 | // Source: src/config/config-state-machine.js 71 | 72 | dgAuth.config(['stateMachineProvider', function(stateMachineProvider) 73 | { 74 | stateMachineProvider.config({ 75 | init: { 76 | transitions: { 77 | run: 'restoringCredentials' 78 | } 79 | }, 80 | restoringCredentials: { 81 | transitions: { 82 | restored: 'settingCredentials' 83 | }, 84 | //Restores the credentials and propagate 85 | action: ['authStorage', 'params', function(authStorage, params) 86 | { 87 | if(authStorage.hasCredentials()) 88 | { 89 | params.credentials = { 90 | username: authStorage.getUsername(), 91 | password: authStorage.getPassword() 92 | }; 93 | } 94 | 95 | return params; 96 | }] 97 | }, 98 | settingCredentials: { 99 | transitions: { 100 | signin: 'loginRequest' 101 | }, 102 | //Sets the credentials as candidate 103 | action: ['authService', 'params', function(authService, params) 104 | { 105 | if(params.hasOwnProperty('credentials')) 106 | { 107 | var credentials = params.credentials; 108 | authService.setCredentials(credentials.username, credentials.password); 109 | } 110 | }] 111 | }, 112 | loginRequest: { 113 | transitions: { 114 | //Checks if the credentials are present(loginError) or not(waitingCredentials) 115 | 401: [ 116 | { 117 | to: 'waitingCredentials', 118 | predicate: ['authService', 'authRequests', function(authService, authRequests) 119 | { 120 | return (!authService.hasCredentials() && authRequests.getValid()); 121 | }] 122 | }, 123 | { 124 | to: 'loginError', 125 | predicate: ['authService', 'authRequests', function(authService, authRequests) 126 | { 127 | return (authService.hasCredentials() && authRequests.getValid()); 128 | }] 129 | }, 130 | { 131 | to: 'failureLogin', 132 | predicate: ['authRequests', function(authRequests) 133 | { 134 | return !authRequests.getValid(); 135 | }] 136 | }], 137 | 201: 'loggedIn' 138 | }, 139 | //Does the request to the server and save the promise 140 | action: ['authRequests', function(authRequests) 141 | { 142 | authRequests.signin(); 143 | }] 144 | }, 145 | loginError: { 146 | transitions: { 147 | submitted: 'settingCredentials' 148 | }, 149 | //Delete the credentials that are invalid and notify the error 150 | action: ['authService', 'params', function(authService, params) 151 | { 152 | authService.clearCredentials(); 153 | var callbacks = authService.getCallbacks('login.error'); 154 | for(var i in callbacks) 155 | { 156 | var callback = callbacks[i]; 157 | callback(params.response); 158 | } 159 | }] 160 | }, 161 | waitingCredentials: { 162 | transitions: { 163 | submitted: 'settingCredentials' 164 | }, 165 | //Checks the previous state and notify the credential need 166 | action: [ 167 | 'authService', 168 | 'authIdentity', 169 | 'authStorage', 170 | 'name', 171 | 'params', 172 | function(authService, authIdentity, authStorage, name, params) 173 | { 174 | if(name == 'logoutRequest') 175 | { 176 | authIdentity.clear(); 177 | authService.clearRequest(); 178 | authService.clearCredentials(); 179 | authStorage.clearCredentials(); 180 | 181 | var callbacksLogout = authService.getCallbacks('logout.successful'); 182 | for(var i in callbacksLogout) 183 | { 184 | var funcSuccessful = callbacksLogout[i]; 185 | funcSuccessful(params.response); 186 | } 187 | } 188 | 189 | authIdentity.suspend(); 190 | authService.clearCredentials(); 191 | authStorage.clearCredentials(); 192 | var callbacksLogin = authService.getCallbacks('login.required'); 193 | for(var j in callbacksLogin) 194 | { 195 | var funcRequest = callbacksLogin[j]; 196 | funcRequest(params.response); 197 | } 198 | }] 199 | }, 200 | loggedIn: { 201 | transitions: { 202 | signout: 'logoutRequest', 203 | 401: 'waitingCredentials' 204 | }, 205 | //Checks the previous state and creates the identity and notify the login successful 206 | action: [ 207 | 'authService', 208 | 'authIdentity', 209 | 'authStorage', 210 | 'name', 211 | 'params', 212 | function(authService, authIdentity, authStorage, name, params) 213 | { 214 | if(name == 'logoutRequest') 215 | { 216 | var callbacksLogout = authService.getCallbacks('logout.error'); 217 | for(var i in callbacksLogout) 218 | { 219 | var funcError = callbacksLogout[i]; 220 | funcError(params.response); 221 | } 222 | } 223 | 224 | if(name == 'loginRequest') 225 | { 226 | if(authIdentity.isSuspended()) 227 | authIdentity.restore(); 228 | 229 | if(!authIdentity.has()) 230 | authIdentity.set(null, params.response.data); 231 | 232 | authService.clearRequest(); 233 | 234 | var credentials = authService.getCredentials(); 235 | authStorage.setCredentials(credentials.username, credentials.password); 236 | 237 | var callbacksLogin = authService.getCallbacks('login.successful'); 238 | for(var j in callbacksLogin) 239 | { 240 | var funcSuccessful = callbacksLogin[j]; 241 | funcSuccessful(params.response); 242 | } 243 | } 244 | }] 245 | }, 246 | logoutRequest: { 247 | transitions: { 248 | 401: 'loggedIn', 249 | 201: 'waitingCredentials' 250 | }, 251 | //Does the request to the server and save the promise 252 | action: ['authRequests', function(authRequests) 253 | { 254 | authRequests.signout(); 255 | }] 256 | }, 257 | failureLogin: { 258 | action: [ 259 | 'authService', 260 | 'authIdentity', 261 | 'params', 262 | function(authService, authIdentity, params) 263 | { 264 | authIdentity.clear(); 265 | authService.clearCredentials(); 266 | 267 | var callbacksLogin = authService.getCallbacks('login.limit'); 268 | for(var j in callbacksLogin) 269 | { 270 | var funcLimit = callbacksLogin[j]; 271 | funcLimit(params.response); 272 | } 273 | }] 274 | } 275 | }); 276 | }]); 277 | 278 | // Source: src/services/dg-auth-service.js 279 | 280 | dgAuth.provider('dgAuthService', function DgAuthServiceProvider() 281 | { 282 | /** 283 | * Class to provide the API to manage 284 | * the module functionality. 285 | * 286 | * @param {Object} $q 287 | * @param {Object} authIdentity 288 | * @param {Object} authRequests 289 | * @param {StateMachine} stateMachine 290 | * @constructor 291 | */ 292 | function DgAuthService($q, authIdentity, authRequests, stateMachine) 293 | { 294 | /** 295 | * Specifies if the service is started. 296 | * 297 | * @type {boolean} 298 | * @private 299 | */ 300 | var _started = false; 301 | 302 | /** 303 | * Starts the service. 304 | */ 305 | this.start = function() 306 | { 307 | stateMachine.initialize(); 308 | 309 | stateMachine.send('run'); 310 | stateMachine.send('restored'); 311 | stateMachine.send('signin'); 312 | 313 | _started = true; 314 | }; 315 | 316 | /** 317 | * Sends a signin message to the state machine. 318 | */ 319 | this.signin = function() 320 | { 321 | if(!_started) 322 | throw 'You have to start te service first'; 323 | 324 | stateMachine.send('signin'); 325 | }; 326 | 327 | /** 328 | * Sends a signout message to the state machine. 329 | */ 330 | this.signout = function() 331 | { 332 | if(!_started) 333 | throw 'You have to start te service first'; 334 | 335 | stateMachine.send('signout'); 336 | }; 337 | 338 | /** 339 | * Sends a submitted message to the state machine 340 | * with the credentials specified. 341 | * 342 | * @param {string} username 343 | * @param {string} password 344 | */ 345 | this.setCredentials = function(username, password) 346 | { 347 | if(!_started) 348 | throw 'You have to start te service first'; 349 | 350 | stateMachine.send('submitted', { 351 | credentials: { 352 | username: username, 353 | password: password 354 | } 355 | }); 356 | }; 357 | 358 | /** 359 | * Checks the authentication. 360 | * 361 | * @returns {promise|false} 362 | */ 363 | this.isAuthorized = function() 364 | { 365 | var deferred = $q.defer(); 366 | 367 | authRequests.getPromise().then(function() 368 | { 369 | deferred.resolve(authIdentity.has()); 370 | }, 371 | function() 372 | { 373 | deferred.reject(authIdentity.has()) 374 | }); 375 | 376 | return deferred.promise; 377 | }; 378 | } 379 | 380 | /** 381 | * Default storage for user credentials. 382 | * 383 | * @type {Storage} 384 | * @private 385 | */ 386 | var _storage = window.sessionStorage; 387 | 388 | /** 389 | * Sets storage for user credentials. 390 | * 391 | * @param storage 392 | */ 393 | this.setStorage = function(storage) 394 | { 395 | _storage = storage; 396 | }; 397 | 398 | /** 399 | * Gets storage for user credentials. 400 | * 401 | * @returns {Storage} 402 | */ 403 | this.getStorage = function() 404 | { 405 | return _storage; 406 | }; 407 | 408 | /** 409 | * The configuration for the login and logout. 410 | * 411 | * @type {Object} 412 | * @private 413 | */ 414 | var _config = { 415 | login: { 416 | method: 'POST', 417 | url: '/signin' 418 | }, 419 | logout: { 420 | method: 'POST', 421 | url: '/signout' 422 | } 423 | }; 424 | 425 | /** 426 | * Sets the configuration for the requests. 427 | * 428 | * @param {Object} config 429 | */ 430 | this.setConfig = function(config) 431 | { 432 | angular.extend(_config, config); 433 | }; 434 | 435 | /** 436 | * Gets the configuration for the requests. 437 | * 438 | * @returns {Object} 439 | */ 440 | this.getConfig = function() 441 | { 442 | return _config; 443 | }; 444 | 445 | /** 446 | * 447 | * @type {number|string} 448 | * @private 449 | */ 450 | var _limit = 4; 451 | 452 | /** 453 | * Sets the limit for the login requests number. 454 | * 455 | * @param {number|string} limit 456 | */ 457 | this.setLimit = function(limit) 458 | { 459 | _limit = limit; 460 | }; 461 | 462 | /** 463 | * Gets the limit for the login requests number. 464 | * 465 | * @returns {number|string} 466 | */ 467 | this.getLimit = function() 468 | { 469 | return _limit; 470 | }; 471 | 472 | /** 473 | * Callbacks configuration. 474 | * 475 | * @type {{login: Array, logout: Array}} 476 | */ 477 | this.callbacks = { 478 | login: [], 479 | logout: [] 480 | }; 481 | 482 | /** 483 | * The header string. 484 | * 485 | * @type {string} 486 | */ 487 | var _header = ''; 488 | 489 | /** 490 | * Sets the header. 491 | * 492 | * @param {String} header 493 | */ 494 | this.setHeader = function(header) 495 | { 496 | _header = header; 497 | }; 498 | 499 | /** 500 | * Gets the header. 501 | * 502 | * @returns {string} 503 | */ 504 | this.getHeader = function() 505 | { 506 | return _header; 507 | }; 508 | 509 | /** 510 | * Gets a new instance of the service. 511 | * 512 | * @type {*[]} 513 | */ 514 | this.$get = ['$q', 'authIdentity', 'authRequests', 'stateMachine', function($q, authIdentity, authRequests, stateMachine) 515 | { 516 | return new DgAuthService($q, authIdentity, authRequests, stateMachine); 517 | }]; 518 | }); 519 | 520 | // Source: src/services/auth-client.js 521 | 522 | /** 523 | * Manages authentication info in the client scope. 524 | */ 525 | dgAuth.factory('authClient', [ 526 | 'authServer', 527 | 'md5', 528 | function(authServer, md5) 529 | { 530 | /** 531 | * Creates the service to use information generating 532 | * header for each request. 533 | * 534 | * @constructor 535 | */ 536 | function AuthClient() 537 | { 538 | /** 539 | * Chars to select when creating nonce. 540 | * 541 | * @type {string} 542 | * @private 543 | */ 544 | var _chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 545 | 546 | /** 547 | * Current counter. 548 | * 549 | * @type {number} 550 | * @private 551 | */ 552 | var _nc = 0; 553 | 554 | /** 555 | * Generates the cnonce with the given length. 556 | * 557 | * @param length Length of the cnonce. 558 | * @returns {string} 559 | */ 560 | var generateNonce = function(length) 561 | { 562 | var nonce = []; 563 | var charsLength = _chars.length; 564 | 565 | for (var i = 0; i < length; ++i) 566 | { 567 | nonce.push(_chars[Math.random() * charsLength | 0]); 568 | } 569 | 570 | return nonce.join(''); 571 | }; 572 | 573 | /** 574 | * Generate the nc progressively for each request. 575 | * 576 | * @returns {string} 577 | */ 578 | var getNc = function() 579 | { 580 | _nc++; 581 | 582 | var zeros = 8 - _nc.toString().length; 583 | 584 | var nc = ""; 585 | for(var i=0; i= 0) 666 | header = generateHeader(username, password, method, url); 667 | } 668 | 669 | return header; 670 | }; 671 | } 672 | 673 | return new AuthClient(); 674 | }]); 675 | 676 | 677 | // Source: src/services/auth-identity.js 678 | 679 | dgAuth.factory('authIdentity', function() 680 | { 681 | function AuthIdentity() 682 | { 683 | /** 684 | * The current identity of user. 685 | * 686 | * @type {Object|null} 687 | * @private 688 | */ 689 | var _identity = null; 690 | 691 | /** 692 | * Specifies if the identity is suspended. 693 | * 694 | * @type {boolean} 695 | * @private 696 | */ 697 | var _suspended = false; 698 | 699 | /** 700 | * Sets the entire identity fields or 701 | * if key is specified, one of these. 702 | * 703 | * @param {string} [key] 704 | * @param {Object|string|Array} value 705 | */ 706 | this.set = function(key, value) 707 | { 708 | if(_suspended) 709 | return; 710 | 711 | if(key) 712 | { 713 | if(null == _identity) 714 | _identity = {}; 715 | 716 | _identity[key] = value; 717 | } 718 | else 719 | { 720 | if(value instanceof Object) 721 | _identity = value; 722 | else 723 | throw 'You have to provide an object if you want to set the identity without a key.'; 724 | } 725 | }; 726 | 727 | /** 728 | * Gets the entire identity of 729 | * if key is specified, one single field. 730 | * 731 | * @param {string} [key] 732 | * @returns {Object|Array|string|null} 733 | */ 734 | this.get = function(key) 735 | { 736 | if(_suspended) 737 | return null; 738 | 739 | if(!key) 740 | return _identity; 741 | 742 | if(!_identity || !_identity.hasOwnProperty(key)) 743 | return null; 744 | 745 | return _identity[key]; 746 | }; 747 | 748 | /** 749 | * Returns true if the identity 750 | * is properly set. 751 | * 752 | * @returns {boolean} 753 | */ 754 | this.has = function() 755 | { 756 | if(_suspended) 757 | return false; 758 | 759 | return (null !== _identity); 760 | }; 761 | 762 | /** 763 | * Clears the identity. 764 | */ 765 | this.clear = function() 766 | { 767 | _identity = null; 768 | }; 769 | 770 | /** 771 | * Suspends the identity. 772 | */ 773 | this.suspend = function() 774 | { 775 | _suspended = true; 776 | }; 777 | 778 | /** 779 | * Restores identity that is 780 | * previously suspended. 781 | */ 782 | this.restore = function() 783 | { 784 | _suspended = false; 785 | }; 786 | 787 | /** 788 | * Checks if the identity is suspended. 789 | * 790 | * @returns {boolean} 791 | */ 792 | this.isSuspended = function() 793 | { 794 | return _suspended; 795 | }; 796 | } 797 | 798 | return new AuthIdentity(); 799 | }); 800 | 801 | // Source: src/services/auth-server.js 802 | 803 | /** 804 | * Parses and provides server information for the authentication. 805 | */ 806 | dgAuth.provider('authServer', ['dgAuthServiceProvider', function AuthServerProvider(dgAuthServiceProvider) 807 | { 808 | /** 809 | * Creates the service for the server info. 810 | * 811 | * @constructor 812 | */ 813 | function AuthServer(header, authStorage) 814 | { 815 | /** 816 | * The header string. 817 | * 818 | * @type {string} 819 | */ 820 | var _header = header; 821 | 822 | /** 823 | * The regular expression to evaluate server information. 824 | * 825 | * @type {RegExp} 826 | * @private 827 | */ 828 | var _valuePattern = /([a-zA-Z]+)=\"?([a-zA-Z0-9\/\s]+)\"?/; 829 | 830 | /** 831 | * True if the header was correctly parsed. 832 | * 833 | * @type {boolean} 834 | * @private 835 | */ 836 | var _configured = false; 837 | 838 | /** 839 | * The configuration of server information. 840 | * 841 | * @type {{realm: string, domain: string, nonce: string, opaque: string, algorithm: string, qop: string}} 842 | */ 843 | this.info = { 844 | realm: '', 845 | domain: '', 846 | nonce: '', 847 | opaque: '', 848 | algorithm: '', 849 | qop: '' 850 | }; 851 | 852 | /** 853 | * Checks if the header was correctly parsed. 854 | * 855 | * @returns {boolean} 856 | */ 857 | this.isConfigured = function() 858 | { 859 | return _configured; 860 | }; 861 | 862 | /** 863 | * Sets the configuration manually. 864 | * 865 | * @param {Object} server The server information. 866 | */ 867 | this.setConfig = function(server) 868 | { 869 | angular.extend(this.info, server); 870 | 871 | _configured = true; 872 | }; 873 | 874 | /** 875 | * Parses header to set the information. 876 | * 877 | * @param {Object} response The response to login request. 878 | */ 879 | this.parseHeader = function(response) 880 | { 881 | var header = response.headers(_header); 882 | 883 | _configured = false; 884 | 885 | if(null !== header) 886 | { 887 | var splitting = header.split(', '); 888 | 889 | for(var i=0; i 2 || split.length == 0) 1037 | throw 'The type for the callbacks is invalid.'; 1038 | 1039 | var family = split[0]; 1040 | var type = (split.length == 2) ? split[1] : null; 1041 | 1042 | var result = []; 1043 | 1044 | if(callbacks.hasOwnProperty(family)) 1045 | { 1046 | var typedCallbacks = callbacks[family]; 1047 | for(var i in typedCallbacks) 1048 | { 1049 | var func = $injector.invoke(typedCallbacks[i]); 1050 | 1051 | if(type) 1052 | { 1053 | if(func.hasOwnProperty(type)) 1054 | result.push(func[type]); 1055 | } 1056 | else 1057 | result.push(func); 1058 | } 1059 | } 1060 | 1061 | return result; 1062 | }; 1063 | } 1064 | 1065 | /** 1066 | * Gets a new instance of AuthService. 1067 | * 1068 | * @type {Array} 1069 | */ 1070 | this.$get = [ 1071 | '$injector', 1072 | /** 1073 | * Gets a new instance of AuthService. 1074 | * 1075 | * @param {Object} $injector 1076 | * @returns {AuthService} 1077 | */ 1078 | function($injector) 1079 | { 1080 | return new AuthService(dgAuthServiceProvider.callbacks, $injector); 1081 | }]; 1082 | 1083 | }]); 1084 | 1085 | // Source: src/services/auth-requests.js 1086 | 1087 | dgAuth.provider('authRequests', ['dgAuthServiceProvider', function AuthRequestsProvider(dgAuthServiceProvider) 1088 | { 1089 | function AuthRequest(limit, config, $http, authService, stateMachine) 1090 | { 1091 | /** 1092 | * 1093 | * 1094 | * @type {promise|null} 1095 | * @private 1096 | */ 1097 | var _promise = null; 1098 | 1099 | /** 1100 | * 1101 | * 1102 | * @returns {promise|null} 1103 | */ 1104 | this.getPromise = function() 1105 | { 1106 | return _promise; 1107 | }; 1108 | 1109 | /** 1110 | * 1111 | * @type {number} 1112 | * @private 1113 | */ 1114 | var _times = 0; 1115 | 1116 | /** 1117 | * 1118 | * @returns {boolean} 1119 | */ 1120 | this.getValid = function() 1121 | { 1122 | if('inf' == limit) 1123 | return true; 1124 | 1125 | return (_times <= limit); 1126 | }; 1127 | 1128 | var request = function() 1129 | { 1130 | var promise = null; 1131 | 1132 | if(authService.hasRequest()) 1133 | { 1134 | var request = authService.getRequest(); 1135 | promise = $http(request.config).then(function(response) 1136 | { 1137 | request.deferred.resolve(response); 1138 | 1139 | if(_times > 0) 1140 | _times = 0; 1141 | 1142 | if(stateMachine.isAvailable('201')) 1143 | stateMachine.send('201', {response: response}); 1144 | 1145 | return response; 1146 | }, 1147 | function(response) 1148 | { 1149 | request.deferred.reject(response); 1150 | 1151 | if(_times > 0) 1152 | _times = 0; 1153 | 1154 | if(stateMachine.isAvailable('failure')) 1155 | stateMachine.send('failure', {response: response}); 1156 | 1157 | return response; 1158 | }); 1159 | } 1160 | 1161 | return promise; 1162 | }; 1163 | 1164 | /** 1165 | * 1166 | * @returns {promise} 1167 | */ 1168 | this.signin = function() 1169 | { 1170 | _times++; 1171 | 1172 | _promise = request(); 1173 | if(_promise) 1174 | return _promise; 1175 | 1176 | _promise = $http(config.login).then(function(response) 1177 | { 1178 | _times = 0; 1179 | stateMachine.send('201', {response: response}); 1180 | 1181 | return response; 1182 | }, 1183 | function(response) 1184 | { 1185 | _times = 0; 1186 | stateMachine.send('failure', {response: response}); 1187 | 1188 | return response; 1189 | }); 1190 | 1191 | return _promise; 1192 | }; 1193 | 1194 | /** 1195 | * 1196 | * @returns {promise} 1197 | */ 1198 | this.signout = function() 1199 | { 1200 | _promise = request(); 1201 | if(_promise) 1202 | return _promise; 1203 | 1204 | _promise = $http(config.logout).then(function(response) 1205 | { 1206 | stateMachine.send('201', {response: response}); 1207 | 1208 | return response; 1209 | }, 1210 | function(response) 1211 | { 1212 | return response; 1213 | }); 1214 | return _promise; 1215 | }; 1216 | } 1217 | 1218 | this.$get = ['$http', 'authService', 'stateMachine', function($http, authService, stateMachine) 1219 | { 1220 | return new AuthRequest(dgAuthServiceProvider.getLimit(), dgAuthServiceProvider.getConfig(), $http, authService, stateMachine); 1221 | }]; 1222 | }]); 1223 | 1224 | // Source: src/services/auth-storage.js 1225 | 1226 | /** 1227 | * Stores information to remember user credentials 1228 | * and server information. 1229 | */ 1230 | dgAuth.provider('authStorage', ['dgAuthServiceProvider', function AuthStorageProvider(dgAuthServiceProvider) 1231 | { 1232 | /** 1233 | * Creates the service for the storage. 1234 | * You can choose the type of storage to 1235 | * save user credential. 1236 | * Server info are always stored in the 1237 | * session. 1238 | * 1239 | * @param {Storage} storage Storage to save user credentials. 1240 | * @constructor 1241 | */ 1242 | function AuthStorage(storage) 1243 | { 1244 | /** 1245 | * The storage for credentials. 1246 | * 1247 | * @type {Storage} 1248 | * @private 1249 | */ 1250 | var _storage = storage; 1251 | 1252 | /** 1253 | * The session storage. 1254 | * 1255 | * @type {Storage} 1256 | * @private 1257 | */ 1258 | var _sessionStorage = window.sessionStorage; 1259 | 1260 | /** 1261 | * Checks if the storage has some credentials. 1262 | * 1263 | * @returns {boolean} 1264 | */ 1265 | this.hasCredentials = function() 1266 | { 1267 | var username = _storage.getItem('username'); 1268 | var password = _storage.getItem('password'); 1269 | 1270 | return ((null !== username && null !== password) && (undefined !== username && undefined !== password)); 1271 | }; 1272 | 1273 | /** 1274 | * Sets the credentials. 1275 | * 1276 | * @param {String} username 1277 | * @param {String} password 1278 | */ 1279 | this.setCredentials = function(username, password) 1280 | { 1281 | _storage.setItem('username', username); 1282 | _storage.setItem('password', password); 1283 | }; 1284 | 1285 | /** 1286 | * Removes the credentials in the storage. 1287 | */ 1288 | this.clearCredentials = function() 1289 | { 1290 | _storage.removeItem('username'); 1291 | _storage.removeItem('password'); 1292 | }; 1293 | 1294 | /** 1295 | * Checks if storage contains the server information. 1296 | * 1297 | * @returns {boolean} 1298 | */ 1299 | this.hasServerAuth = function() 1300 | { 1301 | var value = _sessionStorage.getItem('server'); 1302 | return (null !== value && undefined !== value); 1303 | }; 1304 | 1305 | /** 1306 | * Sets the server information. 1307 | * 1308 | * @param {Object} server 1309 | */ 1310 | this.setServerAuth = function(server) 1311 | { 1312 | _sessionStorage.setItem('server', angular.toJson(server)); 1313 | }; 1314 | 1315 | /** 1316 | * Gets the server information. 1317 | * 1318 | * @returns {Object} 1319 | */ 1320 | this.getServerAuth = function() 1321 | { 1322 | return angular.fromJson(_sessionStorage.getItem('server')); 1323 | }; 1324 | 1325 | /** 1326 | * Gets the username saved in the storage. 1327 | * 1328 | * @returns {String} 1329 | */ 1330 | this.getUsername = function() 1331 | { 1332 | return _storage.getItem('username'); 1333 | }; 1334 | 1335 | /** 1336 | * Gets the password saved in the storage. 1337 | * 1338 | * @returns {String} 1339 | */ 1340 | this.getPassword = function() 1341 | { 1342 | return _storage.getItem('password'); 1343 | }; 1344 | 1345 | /** 1346 | * Clears the storage. 1347 | */ 1348 | this.clear = function() 1349 | { 1350 | _storage.clear(); 1351 | _sessionStorage.clear(); 1352 | }; 1353 | } 1354 | 1355 | /** 1356 | * Gets a new instance of AuthStorage. 1357 | * 1358 | * @returns {AuthStorageProvider.AuthStorage} 1359 | */ 1360 | this.$get = function() 1361 | { 1362 | return new AuthStorage(dgAuthServiceProvider.getStorage()); 1363 | }; 1364 | }]); 1365 | -------------------------------------------------------------------------------- /dist/angular-digest-auth.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AngularJS module to manage HTTP Digest Authentication 3 | * @version v0.4.3 - 2014-02-02 4 | * @link https://github.com/tafax/angular-digest-auth 5 | * @author Matteo Tafani Alunno 6 | * @license MIT License, http://www.opensource.org/licenses/MIT 7 | */ 8 | "use strict";var dgAuth=angular.module("dgAuth",["angular-md5","FSM"]);dgAuth.config(["$httpProvider",function(a){a.interceptors.push(["$q","authService","authClient","authServer","stateMachine",function(a,b,c,d,e){return{request:function(d){var e=b.getCredentials(),f=c.processRequest(e.username,e.password,d.method,d.url);return f&&(d.headers.Authorization=f),d||a.when(d)},responseError:function(c){if(401===c.status){if(!d.parseHeader(c))return a.reject(c);var f=a.defer();return b.setRequest(c.config,f),e.send("401",{response:c}),f.promise}return a.reject(c)}}}])}]),dgAuth.config(["stateMachineProvider",function(a){a.config({init:{transitions:{run:"restoringCredentials"}},restoringCredentials:{transitions:{restored:"settingCredentials"},action:["authStorage","params",function(a,b){return a.hasCredentials()&&(b.credentials={username:a.getUsername(),password:a.getPassword()}),b}]},settingCredentials:{transitions:{signin:"loginRequest"},action:["authService","params",function(a,b){if(b.hasOwnProperty("credentials")){var c=b.credentials;a.setCredentials(c.username,c.password)}}]},loginRequest:{transitions:{401:[{to:"waitingCredentials",predicate:["authService","authRequests",function(a,b){return!a.hasCredentials()&&b.getValid()}]},{to:"loginError",predicate:["authService","authRequests",function(a,b){return a.hasCredentials()&&b.getValid()}]},{to:"failureLogin",predicate:["authRequests",function(a){return!a.getValid()}]}],201:"loggedIn"},action:["authRequests",function(a){a.signin()}]},loginError:{transitions:{submitted:"settingCredentials"},action:["authService","params",function(a,b){a.clearCredentials();var c=a.getCallbacks("login.error");for(var d in c){var e=c[d];e(b.response)}}]},waitingCredentials:{transitions:{submitted:"settingCredentials"},action:["authService","authIdentity","authStorage","name","params",function(a,b,c,d,e){if("logoutRequest"==d){b.clear(),a.clearRequest(),a.clearCredentials(),c.clearCredentials();var f=a.getCallbacks("logout.successful");for(var g in f){var h=f[g];h(e.response)}}b.suspend(),a.clearCredentials(),c.clearCredentials();var i=a.getCallbacks("login.required");for(var j in i){var k=i[j];k(e.response)}}]},loggedIn:{transitions:{signout:"logoutRequest",401:"waitingCredentials"},action:["authService","authIdentity","authStorage","name","params",function(a,b,c,d,e){if("logoutRequest"==d){var f=a.getCallbacks("logout.error");for(var g in f){var h=f[g];h(e.response)}}if("loginRequest"==d){b.isSuspended()&&b.restore(),b.has()||b.set(null,e.response.data),a.clearRequest();var i=a.getCredentials();c.setCredentials(i.username,i.password);var j=a.getCallbacks("login.successful");for(var k in j){var l=j[k];l(e.response)}}}]},logoutRequest:{transitions:{401:"loggedIn",201:"waitingCredentials"},action:["authRequests",function(a){a.signout()}]},failureLogin:{action:["authService","authIdentity","params",function(a,b,c){b.clear(),a.clearCredentials();var d=a.getCallbacks("login.limit");for(var e in d){var f=d[e];f(c.response)}}]}})}]),dgAuth.provider("dgAuthService",function(){function a(a,b,c,d){var e=!1;this.start=function(){d.initialize(),d.send("run"),d.send("restored"),d.send("signin"),e=!0},this.signin=function(){if(!e)throw"You have to start te service first";d.send("signin")},this.signout=function(){if(!e)throw"You have to start te service first";d.send("signout")},this.setCredentials=function(a,b){if(!e)throw"You have to start te service first";d.send("submitted",{credentials:{username:a,password:b}})},this.isAuthorized=function(){var d=a.defer();return c.getPromise().then(function(){d.resolve(b.has())},function(){d.reject(b.has())}),d.promise}}var b=window.sessionStorage;this.setStorage=function(a){b=a},this.getStorage=function(){return b};var c={login:{method:"POST",url:"/signin"},logout:{method:"POST",url:"/signout"}};this.setConfig=function(a){angular.extend(c,a)},this.getConfig=function(){return c};var d=4;this.setLimit=function(a){d=a},this.getLimit=function(){return d},this.callbacks={login:[],logout:[]};var e="";this.setHeader=function(a){e=a},this.getHeader=function(){return e},this.$get=["$q","authIdentity","authRequests","stateMachine",function(b,c,d,e){return new a(b,c,d,e)}]}),dgAuth.factory("authClient",["authServer","md5",function(a,b){function c(){var c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",d=0,e=function(a){for(var b=[],d=c.length,e=0;a>e;++e)b.push(c[Math.random()*d|0]);return b.join("")},f=function(){d++;for(var a=8-d.toString().length,b="",c=0;a>c;c++)b+="0";return b+d},g=function(c,d,e,f,g,h){var i=b.createHash(c+":"+a.info.realm+":"+d),j=b.createHash(e+":"+f);return b.createHash(i+":"+a.info.nonce+":"+g+":"+h+":"+a.info.qop+":"+j)},h=function(b,c,d,h){var i=f(),j=e(16);return'Digest username="'+b+'", realm="'+a.info.realm+'", nonce="'+a.info.nonce+'", uri="'+h+'", algorithm="'+a.info.algorithm+'", response="'+g(b,c,d,h,i,j)+'", opaque="'+a.info.opaque+'", qop="'+a.info.qop+'", nc="'+i+'", cnonce="'+j+'"'};this.isConfigured=function(){return a.isConfigured()},this.processRequest=function(b,c,d,e){var f=null;return this.isConfigured()&&e.indexOf(a.info.domain)>=0&&(f=h(b,c,d,e)),f}}return new c}]),dgAuth.factory("authIdentity",function(){function a(){var a=null,b=!1;this.set=function(c,d){if(!b)if(c)null==a&&(a={}),a[c]=d;else{if(!(d instanceof Object))throw"You have to provide an object if you want to set the identity without a key.";a=d}},this.get=function(c){return b?null:c?a&&a.hasOwnProperty(c)?a[c]:null:a},this.has=function(){return b?!1:null!==a},this.clear=function(){a=null},this.suspend=function(){b=!0},this.restore=function(){b=!1},this.isSuspended=function(){return b}}return new a}),dgAuth.provider("authServer",["dgAuthServiceProvider",function(a){function b(a,b){var c=a,d=/([a-zA-Z]+)=\"?([a-zA-Z0-9\/\s]+)\"?/,e=!1;this.info={realm:"",domain:"",nonce:"",opaque:"",algorithm:"",qop:""},this.isConfigured=function(){return e},this.setConfig=function(a){angular.extend(this.info,a),e=!0},this.parseHeader=function(a){var f=a.headers(c);if(e=!1,null!==f){for(var g=f.split(", "),h=0;h2||0==d.length)throw"The type for the callbacks is invalid.";var e=d[0],f=2==d.length?d[1]:null,g=[];if(a.hasOwnProperty(e)){var h=a[e];for(var i in h){var j=b.invoke(h[i]);f?j.hasOwnProperty(f)&&g.push(j[f]):g.push(j)}}return g}}this.$get=["$injector",function(c){return new b(a.callbacks,c)}]}]),dgAuth.provider("authRequests",["dgAuthServiceProvider",function(a){function b(a,b,c,d,e){var f=null;this.getPromise=function(){return f};var g=0;this.getValid=function(){return"inf"==a?!0:a>=g};var h=function(){var a=null;if(d.hasRequest()){var b=d.getRequest();a=c(b.config).then(function(a){return b.deferred.resolve(a),g>0&&(g=0),e.isAvailable("201")&&e.send("201",{response:a}),a},function(a){return b.deferred.reject(a),g>0&&(g=0),e.isAvailable("failure")&&e.send("failure",{response:a}),a})}return a};this.signin=function(){return g++,(f=h())?f:f=c(b.login).then(function(a){return g=0,e.send("201",{response:a}),a},function(a){return g=0,e.send("failure",{response:a}),a})},this.signout=function(){return(f=h())?f:f=c(b.logout).then(function(a){return e.send("201",{response:a}),a},function(a){return a})}}this.$get=["$http","authService","stateMachine",function(c,d,e){return new b(a.getLimit(),a.getConfig(),c,d,e)}]}]),dgAuth.provider("authStorage",["dgAuthServiceProvider",function(a){function b(a){var b=a,c=window.sessionStorage;this.hasCredentials=function(){var a=b.getItem("username"),c=b.getItem("password");return null!==a&&null!==c&&void 0!==a&&void 0!==c},this.setCredentials=function(a,c){b.setItem("username",a),b.setItem("password",c)},this.clearCredentials=function(){b.removeItem("username"),b.removeItem("password")},this.hasServerAuth=function(){var a=c.getItem("server");return null!==a&&void 0!==a},this.setServerAuth=function(a){c.setItem("server",angular.toJson(a))},this.getServerAuth=function(){return angular.fromJson(c.getItem("server"))},this.getUsername=function(){return b.getItem("username")},this.getPassword=function(){return b.getItem("password")},this.clear=function(){b.clear(),c.clear()}}this.$get=function(){return new b(a.getStorage())}}]); -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Jan 11 2014 13:56:03 GMT+0100 (CET) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path, that will be used to resolve files and exclude 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | frameworks: ['jasmine'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'lib/angular/angular.min.js', 18 | 'lib/angular-mocks/angular-mocks.js', 19 | 'lib/angular-md5/angular-md5.js', 20 | 'lib/angular-state-machine/src/angular-state-machine.js', 21 | 'lib/angular-state-machine/src/services/state-machine.js', 22 | 'src/angular-digest-auth.js', 23 | 'src/config/config-module.js', 24 | 'src/config/config-state-machine.js', 25 | 'src/services/dg-auth-service.js', 26 | 'src/services/auth-client.js', 27 | 'src/services/auth-identity.js', 28 | 'src/services/auth-server.js', 29 | 'src/services/auth-service.js', 30 | 'src/services/auth-requests.js', 31 | 'src/services/auth-storage.js', 32 | 'tests/**/*Spec.js' 33 | ], 34 | 35 | 36 | // list of files to exclude 37 | exclude: [ 38 | 39 | ], 40 | 41 | 42 | // test results reporter to use 43 | // possible values: 'dots', 'progress', 'junit', 'growl', 'coverage' 44 | reporters: ['progress'], 45 | 46 | 47 | // web server port 48 | port: 9876, 49 | 50 | 51 | // enable / disable colors in the output (reporters and logs) 52 | colors: true, 53 | 54 | 55 | // level of logging 56 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 57 | logLevel: config.LOG_INFO, 58 | 59 | 60 | // enable / disable watching file and executing tests whenever any file changes 61 | autoWatch: true, 62 | 63 | 64 | // Start these browsers, currently available: 65 | // - Chrome 66 | // - ChromeCanary 67 | // - Firefox 68 | // - Opera (has to be installed with `npm install karma-opera-launcher`) 69 | // - Safari (only Mac; has to be installed with `npm install karma-safari-launcher`) 70 | // - PhantomJS 71 | // - IE (only Windows; has to be installed with `npm install karma-ie-launcher`) 72 | browsers: ['Chrome', 'Firefox', 'Opera'], 73 | 74 | 75 | // If browser does not capture in given timeout [ms], kill it 76 | captureTimeout: 60000, 77 | 78 | 79 | // Continuous Integration mode 80 | // if true, it capture browsers, run tests and exit 81 | singleRun: false 82 | }); 83 | }; 84 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-digest-auth", 3 | "version": "0.4.3", 4 | "filename": "angular-digest-auth.min.js", 5 | "main": "./dist/angular-digest-auth.min.js", 6 | "homepage": "https://github.com/tafax/angular-digest-auth", 7 | "author": "Matteo Tafani Alunno ", 8 | "repository": { 9 | "type": "git", 10 | "url": "git://github.com/tafax/angular-digest-auth.git" 11 | }, 12 | "devDependencies": { 13 | "grunt-cli": ">= 0.1.7", 14 | "grunt-contrib-uglify": "*", 15 | "grunt-conventional-changelog": "*", 16 | "grunt-bower-task": "~0.3.4", 17 | "grunt-contrib-concat": "~0.3.0", 18 | "grunt-remove-logging": "~0.2.0", 19 | "karma-script-launcher": "~0.1.0", 20 | "karma-chrome-launcher": "~0.1.2", 21 | "karma-firefox-launcher": "~0.1.3", 22 | "karma-html2js-preprocessor": "~0.1.0", 23 | "karma-jasmine": "~0.1.5", 24 | "requirejs": "~2.1.10", 25 | "karma-requirejs": "~0.2.1", 26 | "karma-coffee-preprocessor": "~0.1.2", 27 | "karma-phantomjs-launcher": "~0.1.1", 28 | "karma": "~0.10.9", 29 | "grunt-karma": "~0.6.2", 30 | "grunt-bump": "0.0.13", 31 | "karma-opera-launcher": "~0.1.0" 32 | }, 33 | "description": "AngularJS module to manage HTTP Digest Authentication", 34 | "bugs": { 35 | "url": "https://github.com/tafax/angular-digest-auth/issues" 36 | }, 37 | "scripts": { 38 | "test": "grunt travis --verbose" 39 | }, 40 | "keywords": [ 41 | "angular", 42 | "http digest", 43 | "digest", 44 | "authentication", 45 | "login", 46 | "app" 47 | ], 48 | "license": "MIT" 49 | } 50 | -------------------------------------------------------------------------------- /src/angular-digest-auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * dgAuth provides functionality to manage 5 | * user authentication 6 | */ 7 | var dgAuth = angular.module('dgAuth', ['angular-md5', 'FSM']); -------------------------------------------------------------------------------- /src/config/config-module.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Configures http to intercept requests and responses with error 401. 5 | */ 6 | dgAuth.config(['$httpProvider', function($httpProvider) 7 | { 8 | $httpProvider.interceptors.push([ 9 | '$q', 10 | 'authService', 11 | 'authClient', 12 | 'authServer', 13 | 'stateMachine', 14 | function($q, authService, authClient, authServer, stateMachine) 15 | { 16 | return { 17 | 'request': function(request) 18 | { 19 | var login = authService.getCredentials(); 20 | var header = authClient.processRequest(login.username, login.password, request.method, request.url); 21 | 22 | if(header) 23 | request.headers['Authorization'] = header; 24 | 25 | return (request || $q.when(request)); 26 | }, 27 | 'responseError': function(rejection) 28 | { 29 | if(rejection.status === 401) 30 | { 31 | if(!authServer.parseHeader(rejection)) 32 | { 33 | return $q.reject(rejection); 34 | } 35 | 36 | var deferred = $q.defer(); 37 | 38 | authService.setRequest(rejection.config, deferred); 39 | stateMachine.send('401', {response: rejection}); 40 | 41 | return deferred.promise; 42 | } 43 | 44 | return $q.reject(rejection); 45 | } 46 | }; 47 | }]); 48 | }]); -------------------------------------------------------------------------------- /src/config/config-state-machine.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | dgAuth.config(['stateMachineProvider', function(stateMachineProvider) 4 | { 5 | stateMachineProvider.config({ 6 | init: { 7 | transitions: { 8 | run: 'restoringCredentials' 9 | } 10 | }, 11 | restoringCredentials: { 12 | transitions: { 13 | restored: 'settingCredentials' 14 | }, 15 | //Restores the credentials and propagate 16 | action: ['authStorage', 'params', function(authStorage, params) 17 | { 18 | if(authStorage.hasCredentials()) 19 | { 20 | params.credentials = { 21 | username: authStorage.getUsername(), 22 | password: authStorage.getPassword() 23 | }; 24 | } 25 | 26 | return params; 27 | }] 28 | }, 29 | settingCredentials: { 30 | transitions: { 31 | signin: 'loginRequest' 32 | }, 33 | //Sets the credentials as candidate 34 | action: ['authService', 'params', function(authService, params) 35 | { 36 | if(params.hasOwnProperty('credentials')) 37 | { 38 | var credentials = params.credentials; 39 | authService.setCredentials(credentials.username, credentials.password); 40 | } 41 | }] 42 | }, 43 | loginRequest: { 44 | transitions: { 45 | //Checks if the credentials are present(loginError) or not(waitingCredentials) 46 | 401: [ 47 | { 48 | to: 'waitingCredentials', 49 | predicate: ['authService', 'authRequests', function(authService, authRequests) 50 | { 51 | return (!authService.hasCredentials() && authRequests.getValid()); 52 | }] 53 | }, 54 | { 55 | to: 'loginError', 56 | predicate: ['authService', 'authRequests', function(authService, authRequests) 57 | { 58 | return (authService.hasCredentials() && authRequests.getValid()); 59 | }] 60 | }, 61 | { 62 | to: 'failureLogin', 63 | predicate: ['authRequests', function(authRequests) 64 | { 65 | return !authRequests.getValid(); 66 | }] 67 | }], 68 | 201: 'loggedIn' 69 | }, 70 | //Does the request to the server and save the promise 71 | action: ['authRequests', function(authRequests) 72 | { 73 | authRequests.signin(); 74 | }] 75 | }, 76 | loginError: { 77 | transitions: { 78 | submitted: 'settingCredentials' 79 | }, 80 | //Delete the credentials that are invalid and notify the error 81 | action: ['authService', 'params', function(authService, params) 82 | { 83 | authService.clearCredentials(); 84 | var callbacks = authService.getCallbacks('login.error'); 85 | for(var i in callbacks) 86 | { 87 | var callback = callbacks[i]; 88 | callback(params.response); 89 | } 90 | }] 91 | }, 92 | waitingCredentials: { 93 | transitions: { 94 | submitted: 'settingCredentials' 95 | }, 96 | //Checks the previous state and notify the credential need 97 | action: [ 98 | 'authService', 99 | 'authIdentity', 100 | 'authStorage', 101 | 'name', 102 | 'params', 103 | function(authService, authIdentity, authStorage, name, params) 104 | { 105 | if(name == 'logoutRequest') 106 | { 107 | authIdentity.clear(); 108 | authService.clearRequest(); 109 | authService.clearCredentials(); 110 | authStorage.clearCredentials(); 111 | 112 | var callbacksLogout = authService.getCallbacks('logout.successful'); 113 | for(var i in callbacksLogout) 114 | { 115 | var funcSuccessful = callbacksLogout[i]; 116 | funcSuccessful(params.response); 117 | } 118 | } 119 | 120 | authIdentity.suspend(); 121 | authService.clearCredentials(); 122 | authStorage.clearCredentials(); 123 | var callbacksLogin = authService.getCallbacks('login.required'); 124 | for(var j in callbacksLogin) 125 | { 126 | var funcRequest = callbacksLogin[j]; 127 | funcRequest(params.response); 128 | } 129 | }] 130 | }, 131 | loggedIn: { 132 | transitions: { 133 | signout: 'logoutRequest', 134 | 401: 'waitingCredentials' 135 | }, 136 | //Checks the previous state and creates the identity and notify the login successful 137 | action: [ 138 | 'authService', 139 | 'authIdentity', 140 | 'authStorage', 141 | 'name', 142 | 'params', 143 | function(authService, authIdentity, authStorage, name, params) 144 | { 145 | if(name == 'logoutRequest') 146 | { 147 | var callbacksLogout = authService.getCallbacks('logout.error'); 148 | for(var i in callbacksLogout) 149 | { 150 | var funcError = callbacksLogout[i]; 151 | funcError(params.response); 152 | } 153 | } 154 | 155 | if(name == 'loginRequest') 156 | { 157 | if(authIdentity.isSuspended()) 158 | authIdentity.restore(); 159 | 160 | if(!authIdentity.has()) 161 | authIdentity.set(null, params.response.data); 162 | 163 | authService.clearRequest(); 164 | 165 | var credentials = authService.getCredentials(); 166 | authStorage.setCredentials(credentials.username, credentials.password); 167 | 168 | var callbacksLogin = authService.getCallbacks('login.successful'); 169 | for(var j in callbacksLogin) 170 | { 171 | var funcSuccessful = callbacksLogin[j]; 172 | funcSuccessful(params.response); 173 | } 174 | } 175 | }] 176 | }, 177 | logoutRequest: { 178 | transitions: { 179 | 401: 'loggedIn', 180 | 201: 'waitingCredentials' 181 | }, 182 | //Does the request to the server and save the promise 183 | action: ['authRequests', function(authRequests) 184 | { 185 | authRequests.signout(); 186 | }] 187 | }, 188 | failureLogin: { 189 | action: [ 190 | 'authService', 191 | 'authIdentity', 192 | 'params', 193 | function(authService, authIdentity, params) 194 | { 195 | authIdentity.clear(); 196 | authService.clearCredentials(); 197 | 198 | var callbacksLogin = authService.getCallbacks('login.limit'); 199 | for(var j in callbacksLogin) 200 | { 201 | var funcLimit = callbacksLogin[j]; 202 | funcLimit(params.response); 203 | } 204 | }] 205 | } 206 | }); 207 | }]); -------------------------------------------------------------------------------- /src/services/auth-client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Manages authentication info in the client scope. 3 | */ 4 | dgAuth.factory('authClient', [ 5 | 'authServer', 6 | 'md5', 7 | function(authServer, md5) 8 | { 9 | /** 10 | * Creates the service to use information generating 11 | * header for each request. 12 | * 13 | * @constructor 14 | */ 15 | function AuthClient() 16 | { 17 | /** 18 | * Chars to select when creating nonce. 19 | * 20 | * @type {string} 21 | * @private 22 | */ 23 | var _chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 24 | 25 | /** 26 | * Current counter. 27 | * 28 | * @type {number} 29 | * @private 30 | */ 31 | var _nc = 0; 32 | 33 | /** 34 | * Generates the cnonce with the given length. 35 | * 36 | * @param length Length of the cnonce. 37 | * @returns {string} 38 | */ 39 | var generateNonce = function(length) 40 | { 41 | var nonce = []; 42 | var charsLength = _chars.length; 43 | 44 | for (var i = 0; i < length; ++i) 45 | { 46 | nonce.push(_chars[Math.random() * charsLength | 0]); 47 | } 48 | 49 | return nonce.join(''); 50 | }; 51 | 52 | /** 53 | * Generate the nc progressively for each request. 54 | * 55 | * @returns {string} 56 | */ 57 | var getNc = function() 58 | { 59 | _nc++; 60 | 61 | var zeros = 8 - _nc.toString().length; 62 | 63 | var nc = ""; 64 | for(var i=0; i= 0) 145 | header = generateHeader(username, password, method, url); 146 | } 147 | 148 | return header; 149 | }; 150 | } 151 | 152 | return new AuthClient(); 153 | }]); 154 | -------------------------------------------------------------------------------- /src/services/auth-identity.js: -------------------------------------------------------------------------------- 1 | dgAuth.factory('authIdentity', function() 2 | { 3 | function AuthIdentity() 4 | { 5 | /** 6 | * The current identity of user. 7 | * 8 | * @type {Object|null} 9 | * @private 10 | */ 11 | var _identity = null; 12 | 13 | /** 14 | * Specifies if the identity is suspended. 15 | * 16 | * @type {boolean} 17 | * @private 18 | */ 19 | var _suspended = false; 20 | 21 | /** 22 | * Sets the entire identity fields or 23 | * if key is specified, one of these. 24 | * 25 | * @param {string} [key] 26 | * @param {Object|string|Array} value 27 | */ 28 | this.set = function(key, value) 29 | { 30 | if(_suspended) 31 | return; 32 | 33 | if(key) 34 | { 35 | if(null == _identity) 36 | _identity = {}; 37 | 38 | _identity[key] = value; 39 | } 40 | else 41 | { 42 | if(value instanceof Object) 43 | _identity = value; 44 | else 45 | throw 'You have to provide an object if you want to set the identity without a key.'; 46 | } 47 | }; 48 | 49 | /** 50 | * Gets the entire identity of 51 | * if key is specified, one single field. 52 | * 53 | * @param {string} [key] 54 | * @returns {Object|Array|string|null} 55 | */ 56 | this.get = function(key) 57 | { 58 | if(_suspended) 59 | return null; 60 | 61 | if(!key) 62 | return _identity; 63 | 64 | if(!_identity || !_identity.hasOwnProperty(key)) 65 | return null; 66 | 67 | return _identity[key]; 68 | }; 69 | 70 | /** 71 | * Returns true if the identity 72 | * is properly set. 73 | * 74 | * @returns {boolean} 75 | */ 76 | this.has = function() 77 | { 78 | if(_suspended) 79 | return false; 80 | 81 | return (null !== _identity); 82 | }; 83 | 84 | /** 85 | * Clears the identity. 86 | */ 87 | this.clear = function() 88 | { 89 | _identity = null; 90 | }; 91 | 92 | /** 93 | * Suspends the identity. 94 | */ 95 | this.suspend = function() 96 | { 97 | _suspended = true; 98 | }; 99 | 100 | /** 101 | * Restores identity that is 102 | * previously suspended. 103 | */ 104 | this.restore = function() 105 | { 106 | _suspended = false; 107 | }; 108 | 109 | /** 110 | * Checks if the identity is suspended. 111 | * 112 | * @returns {boolean} 113 | */ 114 | this.isSuspended = function() 115 | { 116 | return _suspended; 117 | }; 118 | } 119 | 120 | return new AuthIdentity(); 121 | }); -------------------------------------------------------------------------------- /src/services/auth-requests.js: -------------------------------------------------------------------------------- 1 | dgAuth.provider('authRequests', ['dgAuthServiceProvider', function AuthRequestsProvider(dgAuthServiceProvider) 2 | { 3 | function AuthRequest(limit, config, $http, authService, stateMachine) 4 | { 5 | /** 6 | * 7 | * 8 | * @type {promise|null} 9 | * @private 10 | */ 11 | var _promise = null; 12 | 13 | /** 14 | * 15 | * 16 | * @returns {promise|null} 17 | */ 18 | this.getPromise = function() 19 | { 20 | return _promise; 21 | }; 22 | 23 | /** 24 | * 25 | * @type {number} 26 | * @private 27 | */ 28 | var _times = 0; 29 | 30 | /** 31 | * 32 | * @returns {boolean} 33 | */ 34 | this.getValid = function() 35 | { 36 | if('inf' == limit) 37 | return true; 38 | 39 | return (_times <= limit); 40 | }; 41 | 42 | var request = function() 43 | { 44 | var promise = null; 45 | 46 | if(authService.hasRequest()) 47 | { 48 | var request = authService.getRequest(); 49 | promise = $http(request.config).then(function(response) 50 | { 51 | request.deferred.resolve(response); 52 | 53 | if(_times > 0) 54 | _times = 0; 55 | 56 | if(stateMachine.isAvailable('201')) 57 | stateMachine.send('201', {response: response}); 58 | 59 | return response; 60 | }, 61 | function(response) 62 | { 63 | request.deferred.reject(response); 64 | 65 | if(_times > 0) 66 | _times = 0; 67 | 68 | if(stateMachine.isAvailable('failure')) 69 | stateMachine.send('failure', {response: response}); 70 | 71 | return response; 72 | }); 73 | } 74 | 75 | return promise; 76 | }; 77 | 78 | /** 79 | * 80 | * @returns {promise} 81 | */ 82 | this.signin = function() 83 | { 84 | _times++; 85 | 86 | _promise = request(); 87 | if(_promise) 88 | return _promise; 89 | 90 | _promise = $http(config.login).then(function(response) 91 | { 92 | _times = 0; 93 | stateMachine.send('201', {response: response}); 94 | 95 | return response; 96 | }, 97 | function(response) 98 | { 99 | _times = 0; 100 | stateMachine.send('failure', {response: response}); 101 | 102 | return response; 103 | }); 104 | 105 | return _promise; 106 | }; 107 | 108 | /** 109 | * 110 | * @returns {promise} 111 | */ 112 | this.signout = function() 113 | { 114 | _promise = request(); 115 | if(_promise) 116 | return _promise; 117 | 118 | _promise = $http(config.logout).then(function(response) 119 | { 120 | stateMachine.send('201', {response: response}); 121 | 122 | return response; 123 | }, 124 | function(response) 125 | { 126 | return response; 127 | }); 128 | return _promise; 129 | }; 130 | } 131 | 132 | this.$get = ['$http', 'authService', 'stateMachine', function($http, authService, stateMachine) 133 | { 134 | return new AuthRequest(dgAuthServiceProvider.getLimit(), dgAuthServiceProvider.getConfig(), $http, authService, stateMachine); 135 | }]; 136 | }]); -------------------------------------------------------------------------------- /src/services/auth-server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses and provides server information for the authentication. 3 | */ 4 | dgAuth.provider('authServer', ['dgAuthServiceProvider', function AuthServerProvider(dgAuthServiceProvider) 5 | { 6 | /** 7 | * Creates the service for the server info. 8 | * 9 | * @constructor 10 | */ 11 | function AuthServer(header, authStorage) 12 | { 13 | /** 14 | * The header string. 15 | * 16 | * @type {string} 17 | */ 18 | var _header = header; 19 | 20 | /** 21 | * The regular expression to evaluate server information. 22 | * 23 | * @type {RegExp} 24 | * @private 25 | */ 26 | var _valuePattern = /([a-zA-Z]+)=\"?([a-zA-Z0-9\/\s]+)\"?/; 27 | 28 | /** 29 | * True if the header was correctly parsed. 30 | * 31 | * @type {boolean} 32 | * @private 33 | */ 34 | var _configured = false; 35 | 36 | /** 37 | * The configuration of server information. 38 | * 39 | * @type {{realm: string, domain: string, nonce: string, opaque: string, algorithm: string, qop: string}} 40 | */ 41 | this.info = { 42 | realm: '', 43 | domain: '', 44 | nonce: '', 45 | opaque: '', 46 | algorithm: '', 47 | qop: '' 48 | }; 49 | 50 | /** 51 | * Checks if the header was correctly parsed. 52 | * 53 | * @returns {boolean} 54 | */ 55 | this.isConfigured = function() 56 | { 57 | return _configured; 58 | }; 59 | 60 | /** 61 | * Sets the configuration manually. 62 | * 63 | * @param {Object} server The server information. 64 | */ 65 | this.setConfig = function(server) 66 | { 67 | angular.extend(this.info, server); 68 | 69 | _configured = true; 70 | }; 71 | 72 | /** 73 | * Parses header to set the information. 74 | * 75 | * @param {Object} response The response to login request. 76 | */ 77 | this.parseHeader = function(response) 78 | { 79 | var header = response.headers(_header); 80 | 81 | _configured = false; 82 | 83 | if(null !== header) 84 | { 85 | var splitting = header.split(', '); 86 | 87 | for(var i=0; i 2 || split.length == 0) 121 | throw 'The type for the callbacks is invalid.'; 122 | 123 | var family = split[0]; 124 | var type = (split.length == 2) ? split[1] : null; 125 | 126 | var result = []; 127 | 128 | if(callbacks.hasOwnProperty(family)) 129 | { 130 | var typedCallbacks = callbacks[family]; 131 | for(var i in typedCallbacks) 132 | { 133 | var func = $injector.invoke(typedCallbacks[i]); 134 | 135 | if(type) 136 | { 137 | if(func.hasOwnProperty(type)) 138 | result.push(func[type]); 139 | } 140 | else 141 | result.push(func); 142 | } 143 | } 144 | 145 | return result; 146 | }; 147 | } 148 | 149 | /** 150 | * Gets a new instance of AuthService. 151 | * 152 | * @type {Array} 153 | */ 154 | this.$get = [ 155 | '$injector', 156 | /** 157 | * Gets a new instance of AuthService. 158 | * 159 | * @param {Object} $injector 160 | * @returns {AuthService} 161 | */ 162 | function($injector) 163 | { 164 | return new AuthService(dgAuthServiceProvider.callbacks, $injector); 165 | }]; 166 | 167 | }]); -------------------------------------------------------------------------------- /src/services/auth-storage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stores information to remember user credentials 3 | * and server information. 4 | */ 5 | dgAuth.provider('authStorage', ['dgAuthServiceProvider', function AuthStorageProvider(dgAuthServiceProvider) 6 | { 7 | /** 8 | * Creates the service for the storage. 9 | * You can choose the type of storage to 10 | * save user credential. 11 | * Server info are always stored in the 12 | * session. 13 | * 14 | * @param {Storage} storage Storage to save user credentials. 15 | * @constructor 16 | */ 17 | function AuthStorage(storage) 18 | { 19 | /** 20 | * The storage for credentials. 21 | * 22 | * @type {Storage} 23 | * @private 24 | */ 25 | var _storage = storage; 26 | 27 | /** 28 | * The session storage. 29 | * 30 | * @type {Storage} 31 | * @private 32 | */ 33 | var _sessionStorage = window.sessionStorage; 34 | 35 | /** 36 | * Checks if the storage has some credentials. 37 | * 38 | * @returns {boolean} 39 | */ 40 | this.hasCredentials = function() 41 | { 42 | var username = _storage.getItem('username'); 43 | var password = _storage.getItem('password'); 44 | 45 | return ((null !== username && null !== password) && (undefined !== username && undefined !== password)); 46 | }; 47 | 48 | /** 49 | * Sets the credentials. 50 | * 51 | * @param {String} username 52 | * @param {String} password 53 | */ 54 | this.setCredentials = function(username, password) 55 | { 56 | _storage.setItem('username', username); 57 | _storage.setItem('password', password); 58 | }; 59 | 60 | /** 61 | * Removes the credentials in the storage. 62 | */ 63 | this.clearCredentials = function() 64 | { 65 | _storage.removeItem('username'); 66 | _storage.removeItem('password'); 67 | }; 68 | 69 | /** 70 | * Checks if storage contains the server information. 71 | * 72 | * @returns {boolean} 73 | */ 74 | this.hasServerAuth = function() 75 | { 76 | var value = _sessionStorage.getItem('server'); 77 | return (null !== value && undefined !== value); 78 | }; 79 | 80 | /** 81 | * Sets the server information. 82 | * 83 | * @param {Object} server 84 | */ 85 | this.setServerAuth = function(server) 86 | { 87 | _sessionStorage.setItem('server', angular.toJson(server)); 88 | }; 89 | 90 | /** 91 | * Gets the server information. 92 | * 93 | * @returns {Object} 94 | */ 95 | this.getServerAuth = function() 96 | { 97 | return angular.fromJson(_sessionStorage.getItem('server')); 98 | }; 99 | 100 | /** 101 | * Gets the username saved in the storage. 102 | * 103 | * @returns {String} 104 | */ 105 | this.getUsername = function() 106 | { 107 | return _storage.getItem('username'); 108 | }; 109 | 110 | /** 111 | * Gets the password saved in the storage. 112 | * 113 | * @returns {String} 114 | */ 115 | this.getPassword = function() 116 | { 117 | return _storage.getItem('password'); 118 | }; 119 | 120 | /** 121 | * Clears the storage. 122 | */ 123 | this.clear = function() 124 | { 125 | _storage.clear(); 126 | _sessionStorage.clear(); 127 | }; 128 | } 129 | 130 | /** 131 | * Gets a new instance of AuthStorage. 132 | * 133 | * @returns {AuthStorageProvider.AuthStorage} 134 | */ 135 | this.$get = function() 136 | { 137 | return new AuthStorage(dgAuthServiceProvider.getStorage()); 138 | }; 139 | }]); 140 | -------------------------------------------------------------------------------- /src/services/dg-auth-service.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | dgAuth.provider('dgAuthService', function DgAuthServiceProvider() 4 | { 5 | /** 6 | * Class to provide the API to manage 7 | * the module functionality. 8 | * 9 | * @param {Object} $q 10 | * @param {Object} authIdentity 11 | * @param {Object} authRequests 12 | * @param {StateMachine} stateMachine 13 | * @constructor 14 | */ 15 | function DgAuthService($q, authIdentity, authRequests, stateMachine) 16 | { 17 | /** 18 | * Specifies if the service is started. 19 | * 20 | * @type {boolean} 21 | * @private 22 | */ 23 | var _started = false; 24 | 25 | /** 26 | * Starts the service. 27 | */ 28 | this.start = function() 29 | { 30 | stateMachine.initialize(); 31 | 32 | stateMachine.send('run'); 33 | stateMachine.send('restored'); 34 | stateMachine.send('signin'); 35 | 36 | _started = true; 37 | }; 38 | 39 | /** 40 | * Sends a signin message to the state machine. 41 | */ 42 | this.signin = function() 43 | { 44 | if(!_started) 45 | throw 'You have to start te service first'; 46 | 47 | stateMachine.send('signin'); 48 | }; 49 | 50 | /** 51 | * Sends a signout message to the state machine. 52 | */ 53 | this.signout = function() 54 | { 55 | if(!_started) 56 | throw 'You have to start te service first'; 57 | 58 | stateMachine.send('signout'); 59 | }; 60 | 61 | /** 62 | * Sends a submitted message to the state machine 63 | * with the credentials specified. 64 | * 65 | * @param {string} username 66 | * @param {string} password 67 | */ 68 | this.setCredentials = function(username, password) 69 | { 70 | if(!_started) 71 | throw 'You have to start te service first'; 72 | 73 | stateMachine.send('submitted', { 74 | credentials: { 75 | username: username, 76 | password: password 77 | } 78 | }); 79 | }; 80 | 81 | /** 82 | * Checks the authentication. 83 | * 84 | * @returns {promise|false} 85 | */ 86 | this.isAuthorized = function() 87 | { 88 | var deferred = $q.defer(); 89 | 90 | authRequests.getPromise().then(function() 91 | { 92 | deferred.resolve(authIdentity.has()); 93 | }, 94 | function() 95 | { 96 | deferred.reject(authIdentity.has()) 97 | }); 98 | 99 | return deferred.promise; 100 | }; 101 | } 102 | 103 | /** 104 | * Default storage for user credentials. 105 | * 106 | * @type {Storage} 107 | * @private 108 | */ 109 | var _storage = window.sessionStorage; 110 | 111 | /** 112 | * Sets storage for user credentials. 113 | * 114 | * @param storage 115 | */ 116 | this.setStorage = function(storage) 117 | { 118 | _storage = storage; 119 | }; 120 | 121 | /** 122 | * Gets storage for user credentials. 123 | * 124 | * @returns {Storage} 125 | */ 126 | this.getStorage = function() 127 | { 128 | return _storage; 129 | }; 130 | 131 | /** 132 | * The configuration for the login and logout. 133 | * 134 | * @type {Object} 135 | * @private 136 | */ 137 | var _config = { 138 | login: { 139 | method: 'POST', 140 | url: '/signin' 141 | }, 142 | logout: { 143 | method: 'POST', 144 | url: '/signout' 145 | } 146 | }; 147 | 148 | /** 149 | * Sets the configuration for the requests. 150 | * 151 | * @param {Object} config 152 | */ 153 | this.setConfig = function(config) 154 | { 155 | angular.extend(_config, config); 156 | }; 157 | 158 | /** 159 | * Gets the configuration for the requests. 160 | * 161 | * @returns {Object} 162 | */ 163 | this.getConfig = function() 164 | { 165 | return _config; 166 | }; 167 | 168 | /** 169 | * 170 | * @type {number|string} 171 | * @private 172 | */ 173 | var _limit = 4; 174 | 175 | /** 176 | * Sets the limit for the login requests number. 177 | * 178 | * @param {number|string} limit 179 | */ 180 | this.setLimit = function(limit) 181 | { 182 | _limit = limit; 183 | }; 184 | 185 | /** 186 | * Gets the limit for the login requests number. 187 | * 188 | * @returns {number|string} 189 | */ 190 | this.getLimit = function() 191 | { 192 | return _limit; 193 | }; 194 | 195 | /** 196 | * Callbacks configuration. 197 | * 198 | * @type {{login: Array, logout: Array}} 199 | */ 200 | this.callbacks = { 201 | login: [], 202 | logout: [] 203 | }; 204 | 205 | /** 206 | * The header string. 207 | * 208 | * @type {string} 209 | */ 210 | var _header = ''; 211 | 212 | /** 213 | * Sets the header. 214 | * 215 | * @param {String} header 216 | */ 217 | this.setHeader = function(header) 218 | { 219 | _header = header; 220 | }; 221 | 222 | /** 223 | * Gets the header. 224 | * 225 | * @returns {string} 226 | */ 227 | this.getHeader = function() 228 | { 229 | return _header; 230 | }; 231 | 232 | /** 233 | * Gets a new instance of the service. 234 | * 235 | * @type {*[]} 236 | */ 237 | this.$get = ['$q', 'authIdentity', 'authRequests', 'stateMachine', function($q, authIdentity, authRequests, stateMachine) 238 | { 239 | return new DgAuthService($q, authIdentity, authRequests, stateMachine); 240 | }]; 241 | }); -------------------------------------------------------------------------------- /tests/angular-digest-authSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('angular-digest-auth', function() 4 | { 5 | var _dgAuthService; 6 | var _stateMachine; 7 | 8 | var _authIdentity; 9 | var _authRequests; 10 | var _authService; 11 | var _authStorage; 12 | var _authServer; 13 | var _md5; 14 | 15 | var _http; 16 | var _httpBackend; 17 | 18 | var _regex = /Digest username\=\"([a-z]*)\"/; 19 | 20 | var _identity = { 21 | id: 1 22 | }; 23 | 24 | var _loginError = { 25 | message: 'Login error.' 26 | }; 27 | 28 | var _logoutError = { 29 | message: 'Logout error.' 30 | }; 31 | 32 | beforeEach(angular.mock.module('dgAuth')); 33 | 34 | beforeEach(function() 35 | { 36 | var fakeModule = angular.module('test.config', []); 37 | fakeModule.config([ 38 | 'dgAuthServiceProvider', 39 | function(dgAuthServiceProvider) 40 | { 41 | dgAuthServiceProvider.setLimit(10); 42 | 43 | dgAuthServiceProvider.setConfig({ 44 | login: { 45 | method: 'POST', 46 | url: '/signin' 47 | }, 48 | logout: { 49 | method: 'POST', 50 | url: '/signout' 51 | } 52 | }); 53 | 54 | dgAuthServiceProvider.callbacks.login.push(['authIdentity', function(authIdentity) 55 | { 56 | return { 57 | successful: function(response) 58 | { 59 | if(response.url == '/signin') 60 | { 61 | expect(response.data).toEqual(_identity); 62 | } 63 | 64 | if(response.url == '/change') 65 | { 66 | expect(response.data).toEqual('OK'); 67 | } 68 | 69 | expect(authIdentity.has()).toEqual(true); 70 | expect(authIdentity.get()).toEqual(_identity); 71 | }, 72 | error: function(response) 73 | { 74 | expect(response.data).toEqual(_loginError); 75 | expect(authIdentity.has()).toEqual(false); 76 | expect(authIdentity.get()).toEqual(null); 77 | }, 78 | required: function(response) 79 | { 80 | expect(authIdentity.has()).toEqual(false); 81 | expect(authIdentity.get()).toEqual(null); 82 | }, 83 | limit: function(response) 84 | { 85 | expect(response.data).toEqual(_loginError); 86 | expect(authIdentity.has()).toEqual(false); 87 | expect(authIdentity.get()).toEqual(null); 88 | } 89 | }; 90 | }]); 91 | 92 | dgAuthServiceProvider.callbacks.logout.push(['authIdentity', function(authIdentity) 93 | { 94 | return { 95 | successful: function(response) 96 | { 97 | expect(response.data).toEqual(''); 98 | expect(authIdentity.has()).toEqual(false); 99 | expect(authIdentity.get()).toEqual(null); 100 | }, 101 | error: function(response) 102 | { 103 | expect(response.data).toEqual(_logoutError); 104 | expect(authIdentity.has()).toEqual(true); 105 | expect(authIdentity.get()).toEqual(_identity); 106 | } 107 | }; 108 | }]); 109 | 110 | dgAuthServiceProvider.setHeader('X-Auth-Digest'); 111 | }]); 112 | 113 | module('dgAuth', 'test.config'); 114 | 115 | inject(function($injector) 116 | { 117 | _dgAuthService = $injector.get('dgAuthService'); 118 | _stateMachine = $injector.get('stateMachine'); 119 | 120 | _authIdentity = $injector.get('authIdentity'); 121 | _authRequests = $injector.get('authRequests'); 122 | _authService = $injector.get('authService'); 123 | _authStorage = $injector.get('authStorage'); 124 | _authServer = $injector.get('authServer'); 125 | _md5 = $injector.get('md5'); 126 | 127 | _http = $injector.get('$http'); 128 | _httpBackend = $injector.get('$httpBackend'); 129 | 130 | var changeCount = 0; 131 | 132 | _httpBackend.whenGET('/change').respond(function(method, url, data, headers) 133 | { 134 | if(changeCount == 0) 135 | { 136 | changeCount++; 137 | 138 | var responseHeaders = { 139 | 'X-Auth-Digest': 'Digest ' + 140 | 'realm="Test AngularJS module", ' + 141 | 'domain="/", ' + 142 | 'nonce="32fffd4e446fc7735c4995154674e9d4", ' + 143 | 'opaque="e66aa41ca5bf6992a5479102cc787bc9", ' + 144 | 'algorithm="MD5", ' + 145 | 'qop="auth"' 146 | }; 147 | 148 | return [401, angular.toJson(_loginError), responseHeaders]; 149 | } 150 | 151 | return [200, 'OK', '']; 152 | }); 153 | 154 | _httpBackend.whenPOST('/signin').respond(function(method, url, data, headers) 155 | { 156 | var authorization = headers.Authorization; 157 | 158 | if(authorization) 159 | { 160 | var regex = new RegExp(_regex); 161 | var username = regex.exec(authorization); 162 | 163 | if(username[1] == 'test') 164 | { 165 | return [201, angular.toJson(_identity), '']; 166 | } 167 | } 168 | 169 | var responseHeaders = { 170 | 'X-Auth-Digest': 'Digest ' + 171 | 'realm="Test AngularJS module", ' + 172 | 'domain="/", ' + 173 | 'nonce="32fffd4e446fc7735c4995154674e9d4", ' + 174 | 'opaque="e66aa41ca5bf6992a5479102cc787bc9", ' + 175 | 'algorithm="MD5", ' + 176 | 'qop="auth"' 177 | }; 178 | 179 | return [401, angular.toJson(_loginError), responseHeaders]; 180 | }); 181 | 182 | _httpBackend.whenPOST('/signout').respond(function(method, url, data, headers) 183 | { 184 | var authorization = headers.Authorization; 185 | if(authorization) 186 | { 187 | var regex = new RegExp(_regex); 188 | var username = regex.exec(authorization); 189 | 190 | if(username[1] == 'test') 191 | { 192 | return [201, '', '']; 193 | } 194 | } 195 | 196 | return [400, angular.toJson(_logoutError), headers]; 197 | }); 198 | }); 199 | }); 200 | 201 | beforeEach(function() 202 | { 203 | _stateMachine.initialize(); 204 | }); 205 | 206 | afterEach(function() 207 | { 208 | _httpBackend.verifyNoOutstandingExpectation(); 209 | _httpBackend.verifyNoOutstandingRequest(); 210 | }); 211 | 212 | describe('tests with no server info or credentials stored', function() 213 | { 214 | beforeEach(function() 215 | { 216 | spyOn(_authStorage, 'hasCredentials').andReturn(false); 217 | 218 | _stateMachine.send('run'); 219 | 220 | expect(_authStorage.hasCredentials).toHaveBeenCalled(); 221 | 222 | spyOn(_authService, 'setCredentials').andCallThrough(); 223 | spyOn(_authService, 'clearCredentials').andCallThrough(); 224 | spyOn(_authService, 'getCallbacks').andCallThrough(); 225 | 226 | _stateMachine.send('restored'); 227 | 228 | expect(_authService.setCredentials).not.toHaveBeenCalled(); 229 | 230 | spyOn(_authRequests, 'signin').andCallThrough(); 231 | }); 232 | 233 | afterEach(function() 234 | { 235 | _stateMachine.send('submitted', { 236 | credentials: { 237 | username: 'test', 238 | password: 'test' 239 | } 240 | }); 241 | 242 | expect(_authService.setCredentials).toHaveBeenCalledWith('test', 'test'); 243 | 244 | spyOn(_authIdentity, 'set').andCallThrough(); 245 | 246 | _stateMachine.send('signin'); 247 | 248 | _httpBackend.expectPOST('/signin'); 249 | _httpBackend.flush(1); 250 | 251 | expect(_authIdentity.set).toHaveBeenCalled(); 252 | expect(_authService.getCallbacks).toHaveBeenCalled(); 253 | 254 | _dgAuthService.isAuthorized().then(function(value) 255 | { 256 | expect(value).toBeTruthy(); 257 | }); 258 | }); 259 | 260 | it('should ask for the info and credentials and then sign in', function() 261 | { 262 | _stateMachine.send('signin'); 263 | 264 | expect(_authRequests.signin).toHaveBeenCalled(); 265 | expect(_authStorage.hasCredentials).toHaveBeenCalled(); 266 | 267 | _httpBackend.expectPOST('/signin'); 268 | _httpBackend.flush(1); 269 | 270 | expect(_authService.clearCredentials).toHaveBeenCalled(); 271 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.required'); 272 | }); 273 | 274 | it('should ask for the info and then have an error on login', function() 275 | { 276 | _stateMachine.send('signin'); 277 | 278 | _dgAuthService.isAuthorized().then(function(value) 279 | { 280 | expect(value).toBeTruthy(); 281 | }); 282 | 283 | expect(_authRequests.signin).toHaveBeenCalled(); 284 | expect(_authStorage.hasCredentials).toHaveBeenCalled(); 285 | 286 | _httpBackend.expectPOST('/signin'); 287 | _httpBackend.flush(1); 288 | 289 | expect(_authService.clearCredentials).toHaveBeenCalled(); 290 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.required'); 291 | 292 | _stateMachine.send('submitted', { 293 | credentials: { 294 | username: 'fake', 295 | password: 'fake' 296 | } 297 | }); 298 | 299 | expect(_authService.setCredentials).toHaveBeenCalledWith('fake', 'fake'); 300 | 301 | _stateMachine.send('signin'); 302 | 303 | _dgAuthService.isAuthorized().then(function(value) 304 | { 305 | expect(value).toBeTruthy(); 306 | }); 307 | 308 | _httpBackend.expectPOST('/signin'); 309 | _httpBackend.flush(1); 310 | 311 | expect(_authService.clearCredentials).toHaveBeenCalled(); 312 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.error'); 313 | }); 314 | }); 315 | 316 | describe('test the limit', function() 317 | { 318 | beforeEach(function() 319 | { 320 | spyOn(_authRequests, 'getValid').andCallThrough(); 321 | spyOn(_authIdentity, 'clear').andCallThrough(); 322 | spyOn(_authStorage, 'hasCredentials').andReturn(false); 323 | 324 | _stateMachine.send('run'); 325 | 326 | expect(_authStorage.hasCredentials).toHaveBeenCalled(); 327 | 328 | spyOn(_authService, 'setCredentials').andCallThrough(); 329 | spyOn(_authService, 'clearCredentials').andCallThrough(); 330 | spyOn(_authService, 'getCallbacks').andCallThrough(); 331 | 332 | _stateMachine.send('restored'); 333 | 334 | expect(_authService.setCredentials).not.toHaveBeenCalled(); 335 | 336 | spyOn(_authRequests, 'signin').andCallThrough(); 337 | 338 | _stateMachine.send('signin'); 339 | 340 | _dgAuthService.isAuthorized().then(function(value) 341 | { 342 | expect(value).toBeTruthy(); 343 | }); 344 | 345 | expect(_authRequests.signin).toHaveBeenCalled(); 346 | expect(_authStorage.hasCredentials).toHaveBeenCalled(); 347 | 348 | _httpBackend.expectPOST('/signin'); 349 | _httpBackend.flush(1); 350 | 351 | expect(_authService.clearCredentials).toHaveBeenCalled(); 352 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.required'); 353 | }); 354 | 355 | it('should have multiple error and exceed the limit', function() 356 | { 357 | for(var i=0; i<10; i++) 358 | { 359 | _stateMachine.send('submitted', { 360 | credentials: { 361 | username: 'fake', 362 | password: 'fake' 363 | } 364 | }); 365 | 366 | expect(_authService.setCredentials).toHaveBeenCalledWith('fake', 'fake'); 367 | 368 | _stateMachine.send('signin'); 369 | 370 | _httpBackend.expectPOST('/signin'); 371 | _httpBackend.flush(1); 372 | 373 | expect(_authService.clearCredentials).toHaveBeenCalled(); 374 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.error'); 375 | } 376 | 377 | expect(_authRequests.getValid).toHaveBeenCalled(); 378 | expect(_authIdentity.clear).toHaveBeenCalled(); 379 | expect(_authService.clearCredentials).toHaveBeenCalled(); 380 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.limit'); 381 | }); 382 | 383 | it('should have multiple error on login', function() 384 | { 385 | for(var i=0; i<8; i++) 386 | { 387 | _stateMachine.send('submitted', { 388 | credentials: { 389 | username: 'fake', 390 | password: 'fake' 391 | } 392 | }); 393 | 394 | expect(_authService.setCredentials).toHaveBeenCalledWith('fake', 'fake'); 395 | 396 | _stateMachine.send('signin'); 397 | 398 | _httpBackend.expectPOST('/signin'); 399 | _httpBackend.flush(1); 400 | 401 | expect(_authService.clearCredentials).toHaveBeenCalled(); 402 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.error'); 403 | } 404 | 405 | _stateMachine.send('submitted', { 406 | credentials: { 407 | username: 'test', 408 | password: 'test' 409 | } 410 | }); 411 | 412 | expect(_authService.setCredentials).toHaveBeenCalledWith('test', 'test'); 413 | 414 | spyOn(_authIdentity, 'set').andCallThrough(); 415 | 416 | _stateMachine.send('signin'); 417 | 418 | _httpBackend.expectPOST('/signin'); 419 | _httpBackend.flush(1); 420 | 421 | expect(_authIdentity.set).toHaveBeenCalled(); 422 | expect(_authService.getCallbacks).toHaveBeenCalled(); 423 | 424 | _dgAuthService.isAuthorized().then(function(value) 425 | { 426 | expect(value).toBeTruthy(); 427 | }); 428 | }); 429 | 430 | it('should have multiple errors and logout successful', function() 431 | { 432 | for(var i=0; i<8; i++) 433 | { 434 | _stateMachine.send('submitted', { 435 | credentials: { 436 | username: 'fake', 437 | password: 'fake' 438 | } 439 | }); 440 | 441 | expect(_authService.setCredentials).toHaveBeenCalledWith('fake', 'fake'); 442 | 443 | _stateMachine.send('signin'); 444 | 445 | _httpBackend.expectPOST('/signin'); 446 | _httpBackend.flush(1); 447 | 448 | expect(_authService.clearCredentials).toHaveBeenCalled(); 449 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.error'); 450 | } 451 | 452 | _stateMachine.send('submitted', { 453 | credentials: { 454 | username: 'test', 455 | password: 'test' 456 | } 457 | }); 458 | 459 | expect(_authService.setCredentials).toHaveBeenCalledWith('test', 'test'); 460 | 461 | spyOn(_authIdentity, 'set').andCallThrough(); 462 | 463 | _stateMachine.send('signin'); 464 | 465 | _httpBackend.expectPOST('/signin'); 466 | _httpBackend.flush(1); 467 | 468 | expect(_authIdentity.set).toHaveBeenCalled(); 469 | expect(_authService.getCallbacks).toHaveBeenCalled(); 470 | 471 | _dgAuthService.isAuthorized().then(function(value) 472 | { 473 | expect(value).toBeTruthy(); 474 | }); 475 | 476 | _stateMachine.send('signout'); 477 | 478 | _dgAuthService.isAuthorized().then(function(value) 479 | { 480 | expect(value).toBeFalsy(); 481 | }); 482 | 483 | _httpBackend.expectPOST('/signout'); 484 | _httpBackend.flush(1); 485 | 486 | expect(_authIdentity.clear).toHaveBeenCalled(); 487 | expect(_authService.getCallbacks).toHaveBeenCalledWith('logout.successful'); 488 | 489 | _dgAuthService.isAuthorized().then(function(value) 490 | { 491 | expect(value).toBeFalsy(); 492 | }); 493 | }); 494 | }); 495 | 496 | describe('tests with server info stored', function() 497 | { 498 | beforeEach(function() 499 | { 500 | _authServer.info = { 501 | realm: 'Test Authentication Realm', 502 | domain: '/', 503 | nonce: _md5.createHash('nonce'), 504 | opaque: _md5.createHash('opaque'), 505 | algorithm: 'MD5', 506 | qop: 'auth' 507 | }; 508 | 509 | spyOn(_authServer, 'isConfigured').andReturn(true); 510 | 511 | spyOn(_authStorage, 'hasCredentials').andReturn(false); 512 | spyOn(_authStorage, 'getUsername'); 513 | spyOn(_authStorage, 'getPassword'); 514 | 515 | _stateMachine.send('run'); 516 | 517 | expect(_authStorage.hasCredentials).toHaveBeenCalled(); 518 | expect(_authStorage.getUsername).not.toHaveBeenCalled(); 519 | expect(_authStorage.getPassword).not.toHaveBeenCalled(); 520 | 521 | spyOn(_authService, 'setCredentials').andCallThrough(); 522 | spyOn(_authService, 'clearCredentials').andCallThrough(); 523 | spyOn(_authService, 'getCallbacks').andCallThrough(); 524 | 525 | _stateMachine.send('restored'); 526 | 527 | expect(_authService.setCredentials).not.toHaveBeenCalled(); 528 | 529 | spyOn(_authRequests, 'signin').andCallThrough(); 530 | }); 531 | 532 | afterEach(function() 533 | { 534 | _stateMachine.send('submitted', { 535 | credentials: { 536 | username: 'test', 537 | password: 'test' 538 | } 539 | }); 540 | 541 | expect(_authService.setCredentials).toHaveBeenCalledWith('test', 'test'); 542 | 543 | spyOn(_authIdentity, 'set').andCallThrough(); 544 | 545 | _stateMachine.send('signin'); 546 | 547 | _httpBackend.expectPOST('/signin'); 548 | _httpBackend.flush(1); 549 | 550 | expect(_authIdentity.set).toHaveBeenCalled(); 551 | expect(_authService.getCallbacks).toHaveBeenCalled(); 552 | 553 | _dgAuthService.isAuthorized().then(function(value) 554 | { 555 | expect(value).toBeTruthy(); 556 | }); 557 | }); 558 | 559 | it('should restore the info and ask for the credentials', function() 560 | { 561 | _stateMachine.send('signin'); 562 | 563 | _dgAuthService.isAuthorized().then(function(value) 564 | { 565 | expect(value).toBeTruthy(); 566 | }); 567 | 568 | expect(_authRequests.signin).toHaveBeenCalled(); 569 | expect(_authStorage.hasCredentials).toHaveBeenCalled(); 570 | 571 | _httpBackend.expectPOST('/signin'); 572 | _httpBackend.flush(1); 573 | 574 | expect(_authService.clearCredentials).toHaveBeenCalled(); 575 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.required'); 576 | }); 577 | }); 578 | 579 | describe('tests with server info and credentials stored', function() 580 | { 581 | beforeEach(function() 582 | { 583 | _authServer.info = { 584 | realm: 'Test Authentication Realm', 585 | domain: '/', 586 | nonce: _md5.createHash('nonce'), 587 | opaque: _md5.createHash('opaque'), 588 | algorithm: 'MD5', 589 | qop: 'auth' 590 | }; 591 | 592 | spyOn(_authServer, 'isConfigured').andReturn(true); 593 | 594 | spyOn(_authStorage, 'hasCredentials').andReturn(true); 595 | spyOn(_authStorage, 'getUsername').andReturn('test'); 596 | spyOn(_authStorage, 'getPassword').andReturn('test'); 597 | 598 | _stateMachine.send('run'); 599 | 600 | expect(_authStorage.hasCredentials).toHaveBeenCalled(); 601 | expect(_authStorage.getUsername).toHaveBeenCalled(); 602 | expect(_authStorage.getPassword).toHaveBeenCalled(); 603 | 604 | spyOn(_authService, 'setCredentials').andCallThrough(); 605 | spyOn(_authService, 'clearCredentials').andCallThrough(); 606 | spyOn(_authService, 'getCallbacks').andCallThrough(); 607 | 608 | _stateMachine.send('restored'); 609 | 610 | expect(_authService.setCredentials).toHaveBeenCalledWith('test', 'test'); 611 | 612 | spyOn(_authIdentity, 'set').andCallThrough(); 613 | spyOn(_authIdentity, 'clear').andCallThrough(); 614 | spyOn(_authRequests, 'signin').andCallThrough(); 615 | }); 616 | 617 | it('should restore the credentials, info and sign in', function() 618 | { 619 | _stateMachine.send('signin'); 620 | 621 | _dgAuthService.isAuthorized().then(function(value) 622 | { 623 | expect(value).toBeTruthy(); 624 | }); 625 | 626 | expect(_authRequests.signin).toHaveBeenCalled(); 627 | 628 | _httpBackend.expectPOST('/signin'); 629 | _httpBackend.flush(1); 630 | 631 | expect(_authIdentity.set).toHaveBeenCalled(); 632 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.successful'); 633 | 634 | _dgAuthService.isAuthorized().then(function(value) 635 | { 636 | expect(value).toBeTruthy(); 637 | }); 638 | }); 639 | 640 | it('should resubmit the credentials', function() 641 | { 642 | spyOn(_authStorage, 'clearCredentials'); 643 | 644 | _stateMachine.send('signin'); 645 | 646 | _dgAuthService.isAuthorized().then(function(value) 647 | { 648 | expect(value).toBeTruthy(); 649 | }); 650 | 651 | expect(_authRequests.signin).toHaveBeenCalled(); 652 | 653 | _httpBackend.expectPOST('/signin'); 654 | _httpBackend.flush(1); 655 | 656 | expect(_authIdentity.set).toHaveBeenCalled(); 657 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.successful'); 658 | 659 | _dgAuthService.isAuthorized().then(function(value) 660 | { 661 | expect(value).toBeTruthy(); 662 | }); 663 | 664 | _http.get('/change').then(function(response) 665 | { 666 | expect(response.data).toEqual('OK'); 667 | }); 668 | 669 | _httpBackend.expectGET('/change'); 670 | _httpBackend.flush(1); 671 | 672 | _dgAuthService.isAuthorized().then(function(value) 673 | { 674 | expect(value).toBeFalsy(); 675 | }); 676 | 677 | expect(_authStorage.clearCredentials).toHaveBeenCalled(); 678 | expect(_authService.clearCredentials).toHaveBeenCalled(); 679 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.required'); 680 | 681 | _stateMachine.send('submitted', { 682 | credentials: { 683 | username: 'test', 684 | password: 'test' 685 | } 686 | }); 687 | 688 | expect(_authService.setCredentials).toHaveBeenCalledWith('test', 'test'); 689 | 690 | _stateMachine.send('signin'); 691 | 692 | _httpBackend.expectGET('/change'); 693 | _httpBackend.flush(1); 694 | 695 | expect(_authIdentity.set).toHaveBeenCalled(); 696 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.successful'); 697 | 698 | _dgAuthService.isAuthorized().then(function(value) 699 | { 700 | expect(value).toBeTruthy(); 701 | }); 702 | }); 703 | 704 | it('should sign out', function() 705 | { 706 | _stateMachine.send('signin'); 707 | 708 | _dgAuthService.isAuthorized().then(function(value) 709 | { 710 | expect(value).toBeTruthy(); 711 | }); 712 | 713 | expect(_authRequests.signin).toHaveBeenCalled(); 714 | 715 | _httpBackend.expectPOST('/signin'); 716 | _httpBackend.flush(1); 717 | 718 | expect(_authIdentity.set).toHaveBeenCalled(); 719 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.successful'); 720 | 721 | _dgAuthService.isAuthorized().then(function(value) 722 | { 723 | expect(value).toBeTruthy(); 724 | }); 725 | 726 | _stateMachine.send('signout'); 727 | 728 | _dgAuthService.isAuthorized().then(function(value) 729 | { 730 | expect(value).toBeFalsy(); 731 | }); 732 | 733 | _httpBackend.expectPOST('/signout'); 734 | _httpBackend.flush(1); 735 | 736 | expect(_authIdentity.clear).toHaveBeenCalled(); 737 | expect(_authService.getCallbacks).toHaveBeenCalledWith('logout.successful'); 738 | 739 | _dgAuthService.isAuthorized().then(function(value) 740 | { 741 | expect(value).toBeFalsy(); 742 | }); 743 | }); 744 | 745 | it('should sign out and then sign in again', function() 746 | { 747 | _stateMachine.send('signin'); 748 | 749 | _dgAuthService.isAuthorized().then(function(value) 750 | { 751 | expect(value).toBeTruthy(); 752 | }); 753 | 754 | expect(_authRequests.signin).toHaveBeenCalled(); 755 | 756 | _httpBackend.expectPOST('/signin'); 757 | _httpBackend.flush(1); 758 | 759 | expect(_authIdentity.set).toHaveBeenCalled(); 760 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.successful'); 761 | 762 | _dgAuthService.isAuthorized().then(function(value) 763 | { 764 | expect(value).toBeTruthy(); 765 | }); 766 | 767 | _stateMachine.send('signout'); 768 | 769 | _dgAuthService.isAuthorized().then(function(value) 770 | { 771 | expect(value).toBeFalsy(); 772 | }); 773 | 774 | _httpBackend.expectPOST('/signout'); 775 | _httpBackend.flush(1); 776 | 777 | expect(_authIdentity.clear).toHaveBeenCalled(); 778 | expect(_authService.getCallbacks).toHaveBeenCalledWith('logout.successful'); 779 | 780 | _dgAuthService.isAuthorized().then(function(value) 781 | { 782 | expect(value).toBeFalsy(); 783 | }); 784 | 785 | _stateMachine.send('submitted', { 786 | credentials: { 787 | username: 'test', 788 | password: 'test' 789 | } 790 | }); 791 | 792 | expect(_authService.setCredentials).toHaveBeenCalledWith('test', 'test'); 793 | 794 | _stateMachine.send('signin'); 795 | 796 | _dgAuthService.isAuthorized().then(function(value) 797 | { 798 | expect(value).toBeTruthy(); 799 | }); 800 | 801 | expect(_authRequests.signin).toHaveBeenCalled(); 802 | 803 | _httpBackend.expectPOST('/signin'); 804 | _httpBackend.flush(1); 805 | 806 | expect(_authIdentity.set).toHaveBeenCalled(); 807 | expect(_authService.getCallbacks).toHaveBeenCalledWith('login.successful'); 808 | 809 | _dgAuthService.isAuthorized().then(function(value) 810 | { 811 | expect(value).toBeTruthy(); 812 | }); 813 | }); 814 | }); 815 | }); -------------------------------------------------------------------------------- /tests/authClientSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Authentication Client Specification', function() 4 | { 5 | var createResponse = function(username, password, method, uri, nc, cnonce) 6 | { 7 | var ha1 = _md5.createHash(username + ":" + _authServer.info.realm + ":" + password); 8 | var ha2 = _md5.createHash(method + ":" + uri); 9 | return _md5.createHash(ha1 + ":" + _authServer.info.nonce + ":" + nc + ":" + cnonce + ":" + _authServer.info.qop + ":" + ha2); 10 | }; 11 | 12 | var _authClient; 13 | 14 | var _authServer; 15 | var _md5; 16 | 17 | var _regex = { 18 | username: /username\=\"([a-zA-Z0-9\s\/]*)\"/, 19 | realm: /realm\=\"([a-zA-Z0-9\s\/]*)\"/, 20 | nonce: /nonce\=\"([a-zA-Z0-9\s\/]*)\"/, 21 | uri: /uri\=\"([a-zA-Z0-9\s\/]*)\"/, 22 | algorithm: /algorithm\=\"([a-zA-Z0-9\s\/]*)\"/, 23 | response: /response\=\"([a-zA-Z0-9\s\/]*)\"/, 24 | opaque: /opaque\=\"([a-zA-Z0-9\s\/]*)\"/, 25 | qop: /qop\=\"([a-zA-Z0-9\s\/]*)\"/, 26 | nc: /nc\=\"([a-zA-Z0-9\s\/]*)\"/, 27 | cnonce: /cnonce\=\"([a-zA-Z0-9\s\/]*)\"/ 28 | }; 29 | 30 | beforeEach(angular.mock.module('dgAuth')); 31 | 32 | beforeEach(function() 33 | { 34 | inject(function($injector) 35 | { 36 | _authClient = $injector.get('authClient'); 37 | 38 | _authServer = $injector.get('authServer'); 39 | _md5 = $injector.get('md5'); 40 | 41 | _authServer.setConfig({ 42 | realm: 'Test Authentication Client', 43 | domain: '/domain', 44 | nonce: _md5.createHash('nonce'), 45 | opaque: _md5.createHash('opaque'), 46 | algorithm: 'MD5', 47 | qop: 'auth' 48 | }); 49 | 50 | spyOn(_authServer, 'isConfigured').andReturn(true); 51 | 52 | spyOn(_md5, 'createHash').andCallThrough(); 53 | }); 54 | }); 55 | 56 | describe('tests the creation of header', function() 57 | { 58 | it('should return null', function() 59 | { 60 | expect(_authClient.processRequest('test', 'test', 'GET', '/some/path')).toBeNull(); 61 | }); 62 | 63 | it('should return the correct header', function() 64 | { 65 | var header = _authClient.processRequest('test', 'test', 'GET', '/domain/some/path'); 66 | 67 | expect(header).not.toBeNull(); 68 | 69 | var results = {}; 70 | for(var i in _regex) 71 | { 72 | var exec = _regex[i].exec(header); 73 | results[i] = exec[1]; 74 | } 75 | 76 | expect(results.username).toEqual('test'); 77 | expect(results.realm).toEqual('Test Authentication Client'); 78 | expect(results.nonce).toEqual(_md5.createHash('nonce')); 79 | expect(results.uri).toEqual('/domain/some/path'); 80 | expect(results.algorithm).toEqual('MD5'); 81 | expect(results.opaque).toEqual(_md5.createHash('opaque')); 82 | expect(results.qop).toEqual('auth'); 83 | expect(results.nc).toEqual('00000001'); 84 | 85 | expect(createResponse('test', 'test', 'GET', '/domain/some/path', results.nc, results.cnonce)).toEqual(results.response); 86 | }); 87 | }); 88 | }); -------------------------------------------------------------------------------- /tests/authIdentitySpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Authentication Identity Specification', function() 4 | { 5 | var _authIdentity; 6 | var _httpBackend; 7 | 8 | var _identity = { 9 | key1: 'value1', 10 | key2: 'value2', 11 | key3: { 12 | subKey1: 'subValue1', 13 | subKey2: 'subValue2' 14 | } 15 | }; 16 | 17 | var _authRequests; 18 | 19 | beforeEach(angular.mock.module('dgAuth')); 20 | 21 | beforeEach(function() 22 | { 23 | inject(function($injector) 24 | { 25 | _authIdentity = $injector.get('authIdentity'); 26 | _authRequests = $injector.get('authRequests'); 27 | _httpBackend = $injector.get('$httpBackend'); 28 | 29 | var http = $injector.get('$http'); 30 | 31 | spyOn(_authRequests, 'getPromise').andCallFake(function() 32 | { 33 | return http.post('/fake'); 34 | }); 35 | 36 | _httpBackend.whenPOST('/fake').respond(function() 37 | { 38 | return [201, '', '']; 39 | }); 40 | }); 41 | }); 42 | 43 | afterEach(function() 44 | { 45 | _httpBackend.verifyNoOutstandingExpectation(); 46 | _httpBackend.verifyNoOutstandingRequest(); 47 | }); 48 | 49 | describe('tests access methods', function() 50 | { 51 | it('should get null values', function() 52 | { 53 | expect(_authIdentity.has()).toBeFalsy(); 54 | expect(_authIdentity.get()).toBeNull(); 55 | expect(_authIdentity.get('some_key')).toBeNull(); 56 | }); 57 | 58 | it('should set the values and gets them', function() 59 | { 60 | _authIdentity.set(null, _identity); 61 | 62 | expect(_authIdentity.has()).toBeTruthy(); 63 | 64 | expect(_authIdentity.get('key1')).toEqual(_identity.key1); 65 | expect(_authIdentity.get('key2')).toEqual(_identity.key2); 66 | expect(_authIdentity.get('key3')).toEqual(_identity.key3); 67 | 68 | expect(_authIdentity.get('fake')).toBeNull(); 69 | 70 | expect(_authIdentity.get()).toEqual(_identity); 71 | }); 72 | 73 | it('should set one value and gets it', function() 74 | { 75 | _authIdentity.set('key', 'value'); 76 | 77 | expect(_authIdentity.has()).toBeTruthy(); 78 | expect(_authIdentity.get('key')).toEqual('value'); 79 | 80 | _authIdentity.set(null, _identity); 81 | 82 | expect(_authIdentity.get()).toEqual(_identity); 83 | expect(_authIdentity.get('key')).toBeNull(); 84 | }); 85 | 86 | it('should clear the identity', function() 87 | { 88 | _authIdentity.set(null, _identity); 89 | 90 | expect(_authIdentity.has()).toBeTruthy(); 91 | 92 | _authIdentity.clear(); 93 | 94 | expect(_authIdentity.has()).toBeFalsy(); 95 | expect(_authIdentity.get()).toBeNull(); 96 | }); 97 | }); 98 | }); -------------------------------------------------------------------------------- /tests/authRequestsSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Authentication Requests Specifications', function() 4 | { 5 | var _authRequests; 6 | 7 | var _stateMachine; 8 | var _authService; 9 | var _authServer; 10 | 11 | var _http; 12 | var _httpBackend; 13 | var _q; 14 | 15 | var _fail = false; 16 | 17 | beforeEach(angular.mock.module('dgAuth')); 18 | 19 | beforeEach(function() 20 | { 21 | inject(function($injector) 22 | { 23 | _authRequests = $injector.get('authRequests'); 24 | _stateMachine = $injector.get('stateMachine'); 25 | 26 | _authService = $injector.get('authService'); 27 | _authServer = $injector.get('authServer'); 28 | 29 | _httpBackend = $injector.get('$httpBackend'); 30 | _http = $injector.get('$http'); 31 | _q = $injector.get('$q'); 32 | 33 | spyOn(_stateMachine, 'send'); 34 | 35 | _httpBackend.whenPOST('/signin').respond(function() 36 | { 37 | if(_fail) 38 | return [401, '401', '']; 39 | 40 | return [201, '201', '']; 41 | }); 42 | 43 | _httpBackend.whenPOST('/signout').respond(function() 44 | { 45 | return [201, '201', '']; 46 | }); 47 | 48 | _httpBackend.whenGET('/request').respond(function() 49 | { 50 | return [201, '201', '']; 51 | }); 52 | }); 53 | }); 54 | 55 | afterEach(function() 56 | { 57 | _httpBackend.verifyNoOutstandingExpectation(); 58 | _httpBackend.verifyNoOutstandingRequest(); 59 | }); 60 | 61 | describe('tests access methods', function() 62 | { 63 | it('should return null', function() 64 | { 65 | expect(_authRequests.getPromise()).toBeNull(); 66 | }); 67 | }); 68 | 69 | describe('tests login http requests', function() 70 | { 71 | it('should return the specified response', function() 72 | { 73 | var promise = _authRequests.signin(); 74 | 75 | expect(_authRequests.getPromise()).toEqual(promise); 76 | 77 | promise.then(function(response) 78 | { 79 | expect(response.data).toEqual('201'); 80 | expect(_stateMachine.send).toHaveBeenCalledWith('201', {response: response}); 81 | }); 82 | 83 | _httpBackend.expectPOST('/signin'); 84 | _httpBackend.flush(1); 85 | 86 | promise.then(function(response) 87 | { 88 | expect(response.data).toEqual('201'); 89 | expect(_stateMachine.send).toHaveBeenCalledWith('201', {response: response}); 90 | }); 91 | }); 92 | 93 | it('should do the request previous saved', function() 94 | { 95 | spyOn(_stateMachine, 'isAvailable').andReturn(true); 96 | 97 | spyOn(_authService, 'hasRequest').andReturn(true); 98 | spyOn(_authService, 'getRequest').andCallFake(function() 99 | { 100 | var deferred = _q.defer(); 101 | 102 | deferred.promise.then(function(response) 103 | { 104 | expect(response.data).toEqual('201'); 105 | 106 | return response; 107 | }); 108 | 109 | return { 110 | config: { 111 | method: 'GET', 112 | url: '/request' 113 | }, 114 | deferred: deferred 115 | }; 116 | }); 117 | 118 | var promise = _authRequests.signin(); 119 | 120 | expect(_authRequests.getPromise()).toEqual(promise); 121 | 122 | promise.then(function(response) 123 | { 124 | expect(response.data).toEqual('201'); 125 | }); 126 | 127 | _httpBackend.expectGET('/request'); 128 | _httpBackend.flush(1); 129 | 130 | expect(_stateMachine.isAvailable).toHaveBeenCalled(); 131 | expect(_stateMachine.send).toHaveBeenCalled(); 132 | }); 133 | }); 134 | 135 | describe('tests logout http requests', function() 136 | { 137 | it('should return the specified response', function() 138 | { 139 | var promise = _authRequests.signout(); 140 | 141 | expect(_authRequests.getPromise()).toEqual(promise); 142 | 143 | promise.then(function(response) 144 | { 145 | expect(response.data).toEqual('201'); 146 | expect(_stateMachine.send).toHaveBeenCalledWith('201', {response: response}); 147 | }); 148 | 149 | _httpBackend.expectPOST('/signout'); 150 | _httpBackend.flush(1); 151 | 152 | promise.then(function(response) 153 | { 154 | expect(response.data).toEqual('201'); 155 | expect(_stateMachine.send).toHaveBeenCalledWith('201', {response: response}); 156 | }); 157 | }); 158 | 159 | it('should do the request previous saved', function() 160 | { 161 | spyOn(_stateMachine, 'isAvailable').andReturn(false); 162 | 163 | spyOn(_authService, 'hasRequest').andReturn(true); 164 | spyOn(_authService, 'getRequest').andCallFake(function() 165 | { 166 | var deferred = _q.defer(); 167 | 168 | deferred.promise.then(function(response) 169 | { 170 | expect(response.data).toEqual('201'); 171 | 172 | return response; 173 | }); 174 | 175 | return { 176 | config: { 177 | method: 'GET', 178 | url: '/request' 179 | }, 180 | deferred: deferred 181 | }; 182 | }); 183 | 184 | var promise = _authRequests.signout(); 185 | 186 | expect(_authRequests.getPromise()).toEqual(promise); 187 | 188 | promise.then(function(response) 189 | { 190 | expect(response.data).toEqual('201'); 191 | }); 192 | 193 | _httpBackend.expectGET('/request'); 194 | _httpBackend.flush(1); 195 | 196 | expect(_stateMachine.isAvailable).toHaveBeenCalled(); 197 | }); 198 | }); 199 | 200 | describe('limit set to default', function() 201 | { 202 | beforeEach(function() 203 | { 204 | _fail = true; 205 | 206 | spyOn(_authServer, 'parseHeader').andReturn(true); 207 | }); 208 | 209 | it('should respect the limit', function() 210 | { 211 | for(var i=0; i<4; i++) 212 | { 213 | _authRequests.signin(); 214 | 215 | _httpBackend.expectPOST('/signin'); 216 | _httpBackend.flush(1); 217 | 218 | expect(_stateMachine.send).toHaveBeenCalled(); 219 | expect(_authRequests.getValid()).toBeTruthy(); 220 | } 221 | }); 222 | 223 | it('should exceed the limit', function() 224 | { 225 | for(var i=0; i<4; i++) 226 | { 227 | _authRequests.signin(); 228 | 229 | _httpBackend.expectPOST('/signin'); 230 | _httpBackend.flush(1); 231 | 232 | expect(_stateMachine.send).toHaveBeenCalled(); 233 | expect(_authRequests.getValid()).toBeTruthy(); 234 | } 235 | 236 | _authRequests.signin(); 237 | 238 | _httpBackend.expectPOST('/signin'); 239 | _httpBackend.flush(1); 240 | 241 | expect(_stateMachine.send).toHaveBeenCalled(); 242 | expect(_authRequests.getValid()).toBeFalsy(); 243 | }); 244 | }); 245 | }); -------------------------------------------------------------------------------- /tests/authServerSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Authentication Server Specification', function() 4 | { 5 | var _authServer; 6 | 7 | var _md5; 8 | var _authStorage; 9 | 10 | var _info; 11 | 12 | function Response(info) 13 | { 14 | var _header = { 15 | "My-Header": "Digest " + 16 | "realm=\"" + info.realm + "\", " + 17 | "domain=\"" + info.domain + "\", " + 18 | "nonce=\"" + info.nonce + "\", " + 19 | "opaque=\"" + info.opaque + "\", " + 20 | "algorithm=\"" + info.algorithm + "\", " + 21 | "qop=\"" + info.qop + "\"" 22 | }; 23 | 24 | return { 25 | headers: function(header) 26 | { 27 | return _header[header]; 28 | } 29 | }; 30 | } 31 | 32 | beforeEach(angular.mock.module('dgAuth')); 33 | 34 | beforeEach(function() 35 | { 36 | var fake = angular.module('test.config', []); 37 | fake.config(['dgAuthServiceProvider', function(dgAuthServiceProvider) 38 | { 39 | dgAuthServiceProvider.setHeader('My-Header'); 40 | }]); 41 | 42 | module('test.config', 'dgAuth'); 43 | 44 | inject(function($injector) 45 | { 46 | _authServer = $injector.get('authServer'); 47 | 48 | _md5 = $injector.get('md5'); 49 | _authStorage = $injector.get('authStorage'); 50 | 51 | spyOn(_authStorage, 'setServerAuth'); 52 | 53 | _info = { 54 | realm: 'Test Authentication Realm', 55 | domain: '/domain', 56 | nonce: _md5.createHash('nonce'), 57 | opaque: _md5.createHash('opaque'), 58 | algorithm: 'MD5', 59 | qop: 'auth' 60 | }; 61 | }); 62 | }); 63 | 64 | describe('tests all methods', function() 65 | { 66 | it('should set the information with manual configuration', function() 67 | { 68 | _authServer.setConfig(_info); 69 | 70 | expect(_authServer.isConfigured()).toBeTruthy(); 71 | expect(_authServer.info).toEqual(_info); 72 | }); 73 | 74 | it('should set the information with response', function() 75 | { 76 | _authServer.parseHeader(new Response(_info)); 77 | 78 | expect(_authStorage.setServerAuth).toHaveBeenCalled(); 79 | 80 | expect(_authServer.isConfigured()).toBeTruthy(); 81 | expect(_authServer.info).toEqual(_info); 82 | }); 83 | }); 84 | }); -------------------------------------------------------------------------------- /tests/authServiceSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Authentication Service Specification', function() 4 | { 5 | var _authService; 6 | 7 | var _log; 8 | 9 | beforeEach(angular.mock.module('dgAuth')); 10 | 11 | beforeEach(function() 12 | { 13 | var fake = angular.module('test.config', []); 14 | fake.config(['dgAuthServiceProvider', function(dgAuthServiceProvider) 15 | { 16 | dgAuthServiceProvider.callbacks.login.push(['$log', function($log) 17 | { 18 | return { 19 | successful: function() 20 | { 21 | return $log; 22 | }, 23 | error: function() 24 | { 25 | return $log; 26 | }, 27 | request: function() 28 | { 29 | return $log; 30 | } 31 | }; 32 | }]); 33 | 34 | dgAuthServiceProvider.callbacks.login.push(['$log', function($log) 35 | { 36 | return { 37 | successful: function() 38 | { 39 | return $log; 40 | }, 41 | error: function() 42 | { 43 | return $log; 44 | }, 45 | request: function() 46 | { 47 | return $log; 48 | } 49 | }; 50 | }]); 51 | 52 | dgAuthServiceProvider.callbacks.logout.push(['$log', function($log) 53 | { 54 | return { 55 | successful: function() 56 | { 57 | return $log; 58 | }, 59 | error: function() 60 | { 61 | return $log; 62 | } 63 | }; 64 | }]); 65 | 66 | dgAuthServiceProvider.callbacks.logout.push(['$log', function($log) 67 | { 68 | return { 69 | successful: function() 70 | { 71 | return $log; 72 | }, 73 | error: function() 74 | { 75 | return $log; 76 | } 77 | }; 78 | }]); 79 | }]); 80 | 81 | module('test.config', 'dgAuth'); 82 | 83 | inject(function($injector) 84 | { 85 | _authService = $injector.get('authService'); 86 | 87 | _log = $injector.get('$log'); 88 | }); 89 | }); 90 | 91 | describe('tests the request methods', function() 92 | { 93 | it('should return null', function() 94 | { 95 | expect(_authService.hasRequest()).toBeFalsy(); 96 | expect(_authService.getRequest()).toBeNull(); 97 | }); 98 | 99 | it('should return the request', function() 100 | { 101 | var config = { 102 | method: 'GET', 103 | url: '/path' 104 | }; 105 | 106 | var deferred = Object(); 107 | 108 | _authService.setRequest(config, deferred); 109 | expect(_authService.hasRequest()).toBeTruthy(); 110 | expect(_authService.getRequest()).toEqual({ 111 | config: config, 112 | deferred: deferred 113 | }); 114 | }); 115 | 116 | it('should clear the request', function() 117 | { 118 | var config = { 119 | method: 'GET', 120 | url: '/path' 121 | }; 122 | 123 | var deferred = Object(); 124 | 125 | _authService.setRequest(config, deferred); 126 | _authService.clearRequest(); 127 | 128 | expect(_authService.hasRequest()).toBeFalsy(); 129 | expect(_authService.getRequest()).toBeNull(); 130 | }); 131 | }); 132 | 133 | describe('tests the credentials', function() 134 | { 135 | it('should return empty values', function() 136 | { 137 | expect(_authService.hasCredentials()).toBeFalsy(); 138 | expect(_authService.getCredentials()).toEqual({ 139 | username: '', 140 | password: '' 141 | }); 142 | }); 143 | 144 | it('should return the credentials', function() 145 | { 146 | _authService.setCredentials('username', 'password'); 147 | 148 | expect(_authService.hasCredentials()).toBeTruthy(); 149 | expect(_authService.getCredentials()).toEqual({ 150 | username: 'username', 151 | password: 'password' 152 | }); 153 | }); 154 | 155 | it('should return empty values even if they are set', function() 156 | { 157 | _authService.setCredentials(' ', ' '); 158 | 159 | expect(_authService.hasCredentials()).toBeFalsy(); 160 | expect(_authService.getCredentials()).toEqual({ 161 | username: '', 162 | password: '' 163 | }); 164 | }); 165 | 166 | it('should clear the credentials', function() 167 | { 168 | _authService.setCredentials('username', 'password'); 169 | 170 | expect(_authService.hasCredentials()).toBeTruthy(); 171 | expect(_authService.getCredentials()).toEqual({ 172 | username: 'username', 173 | password: 'password' 174 | }); 175 | 176 | _authService.clearCredentials(); 177 | 178 | expect(_authService.hasCredentials()).toBeFalsy(); 179 | expect(_authService.getCredentials()).toEqual({ 180 | username: '', 181 | password: '' 182 | }); 183 | }); 184 | }); 185 | 186 | describe('tests the login callbacks', function() 187 | { 188 | it('should return the login callbacks', function() 189 | { 190 | var callbacks = _authService.getCallbacks('login'); 191 | 192 | expect(callbacks.length).toEqual(2); 193 | 194 | for(var i in callbacks) 195 | { 196 | var callback = callbacks[i]; 197 | expect(callback.successful()).toEqual(_log); 198 | expect(callback.error()).toEqual(_log); 199 | expect(callback.request()).toEqual(_log); 200 | } 201 | }); 202 | 203 | it('should return the login successful callbacks', function() 204 | { 205 | var successful = _authService.getCallbacks('login.successful'); 206 | 207 | expect(successful.length).toEqual(2); 208 | 209 | for(var j in successful) 210 | { 211 | var func = successful[j]; 212 | expect(func()).toEqual(_log); 213 | } 214 | }); 215 | 216 | it('should return the login error callbacks', function() 217 | { 218 | var error = _authService.getCallbacks('login.error'); 219 | 220 | expect(error.length).toEqual(2); 221 | 222 | for(var j in error) 223 | { 224 | var func = error[j]; 225 | expect(func()).toEqual(_log); 226 | } 227 | }); 228 | 229 | it('should return the login request callbacks', function() 230 | { 231 | var request = _authService.getCallbacks('login.request'); 232 | 233 | expect(request.length).toEqual(2); 234 | 235 | for(var j in request) 236 | { 237 | var func = request[j]; 238 | expect(func()).toEqual(_log); 239 | } 240 | }); 241 | }); 242 | 243 | describe('tests the logout callbacks', function() 244 | { 245 | it('should return the logout callbacks', function() 246 | { 247 | var callbacks = _authService.getCallbacks('logout'); 248 | 249 | expect(callbacks.length).toEqual(2); 250 | 251 | for(var i in callbacks) 252 | { 253 | var callback = callbacks[i]; 254 | expect(callback.successful()).toEqual(_log); 255 | expect(callback.error()).toEqual(_log); 256 | } 257 | }); 258 | 259 | it('should return the logout successful callbacks', function() 260 | { 261 | var successful = _authService.getCallbacks('logout.successful'); 262 | 263 | expect(successful.length).toEqual(2); 264 | 265 | for(var j in successful) 266 | { 267 | var func = successful[j]; 268 | expect(func()).toEqual(_log); 269 | } 270 | }); 271 | 272 | it('should return the logout error callbacks', function() 273 | { 274 | var error = _authService.getCallbacks('logout.error'); 275 | 276 | expect(error.length).toEqual(2); 277 | 278 | for(var j in error) 279 | { 280 | var func = error[j]; 281 | expect(func()).toEqual(_log); 282 | } 283 | }); 284 | }); 285 | }); -------------------------------------------------------------------------------- /tests/authStorageSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('Authentication Storage Specifications', function() 4 | { 5 | var _mockup = function() 6 | { 7 | var _table = {}; 8 | 9 | return { 10 | getItem: function(key) 11 | { 12 | return _table[key]; 13 | }, 14 | setItem: function(key, value) 15 | { 16 | _table[key] = value.toString(); 17 | }, 18 | removeItem: function(key, value) 19 | { 20 | delete _table[key]; 21 | }, 22 | clear: function() 23 | { 24 | _table = {}; 25 | } 26 | }; 27 | }(); 28 | 29 | var _authStorage; 30 | 31 | var _serverInfo = { 32 | info: 'info', 33 | value: 'value' 34 | }; 35 | 36 | beforeEach(angular.mock.module('dgAuth')); 37 | 38 | beforeEach(function() 39 | { 40 | Object.defineProperty(window, 'sessionStorage', { value: _mockup }); 41 | 42 | spyOn(window.sessionStorage, 'setItem').andCallThrough(); 43 | spyOn(window.sessionStorage, 'getItem').andCallThrough(); 44 | spyOn(window.sessionStorage, 'removeItem').andCallThrough(); 45 | 46 | inject(function($injector) 47 | { 48 | _authStorage = $injector.get('authStorage'); 49 | }); 50 | }); 51 | 52 | describe('tests all access methods', function() 53 | { 54 | it('should return undefined server info', function() 55 | { 56 | expect(_authStorage.hasServerAuth()).toBeFalsy(); 57 | expect(_authStorage.getServerAuth()).toBeUndefined(); 58 | 59 | expect(window.sessionStorage.getItem).toHaveBeenCalledWith('server'); 60 | }); 61 | 62 | it('should set the server info', function() 63 | { 64 | _authStorage.setServerAuth(_serverInfo); 65 | 66 | expect(window.sessionStorage.setItem).toHaveBeenCalledWith('server', angular.toJson(_serverInfo)); 67 | 68 | expect(_authStorage.hasServerAuth()).toBeTruthy(); 69 | expect(_authStorage.getServerAuth()).toEqual(_serverInfo); 70 | }); 71 | 72 | it('should return undefined credentials', function() 73 | { 74 | expect(_authStorage.hasCredentials()).toBeFalsy(); 75 | expect(_authStorage.getUsername()).toBeUndefined(); 76 | expect(_authStorage.getPassword()).toBeUndefined(); 77 | 78 | expect(window.sessionStorage.getItem).toHaveBeenCalledWith('username'); 79 | expect(window.sessionStorage.getItem).toHaveBeenCalledWith('password'); 80 | }); 81 | 82 | it('should return the specified credentials', function() 83 | { 84 | _authStorage.setCredentials('test', 'test'); 85 | 86 | expect(window.sessionStorage.setItem).toHaveBeenCalledWith('username', 'test'); 87 | expect(window.sessionStorage.setItem).toHaveBeenCalledWith('password', 'test'); 88 | 89 | expect(_authStorage.getUsername()).toEqual('test'); 90 | expect(_authStorage.getPassword()).toEqual('test'); 91 | 92 | expect(window.sessionStorage.getItem).toHaveBeenCalledWith('username'); 93 | expect(window.sessionStorage.getItem).toHaveBeenCalledWith('password'); 94 | }); 95 | 96 | it('should clear the credentials', function() 97 | { 98 | _authStorage.setCredentials('test', 'test'); 99 | 100 | expect(_authStorage.hasServerAuth()).toBeTruthy(); 101 | expect(_authStorage.getUsername()).toEqual('test'); 102 | expect(_authStorage.getPassword()).toEqual('test'); 103 | 104 | _authStorage.clearCredentials(); 105 | 106 | expect(window.sessionStorage.removeItem).toHaveBeenCalledWith('username'); 107 | expect(window.sessionStorage.removeItem).toHaveBeenCalledWith('password'); 108 | 109 | expect(_authStorage.hasCredentials()).toBeFalsy(); 110 | expect(_authStorage.getUsername()).toBeUndefined(); 111 | expect(_authStorage.getPassword()).toBeUndefined(); 112 | }); 113 | }); 114 | }); --------------------------------------------------------------------------------