diff --git a/README.md b/README.md
index 96f193b..8fcf0ed 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,80 @@
-angular-digest-interceptor
-==========================
-
AngularJS Digest Authentication Interceptor
+===========================================
+
+Step-by-step
+------------
+1. Client request (no authentication)
+2. Server responds with a 401 'Unauthorized' message, with realm and nonce in the WWW-Authenticate header
+3. Interceptor received the 401 response and generates authrozation header, based on realm, nonce and username/password stored in the LocalStorage
+4. Interceptor generates the original request with authorization headers and stores the hashed authrozation in the LocalStorage
+5. Server responds with 200 'Success' if the credentials are correct. Otherwise the interceptor clears the LocalStorage and redirects to /login.
+
+
+Todo
+----
+- Add original data/json to the second request
+
+
+Example
+-------
+### client.js
+Login controller:
+ .controller('LoginFormCtrl', function ($scope, $http, localStorageService) {
+ $scope.submit = function () {
+ localStorageService.add('email', $scope.email);
+ localStorageService.add('password', $scope.password);
+
+ $http.post('http://127.0.0.1:3000/api/login', {
+ headers: {'Content-Type': 'application/json'}
+ }).then(function (response) {
+ console.log('success', response);
+ }, function (response) {
+ console.log('error', response);
+ });
+ };
+ });
+
+
+App config:
+ $httpProvider.interceptors.push('digestAuthInterceptor');
+
+
+### server.js
+ passport.use(new DigestStrategy({
+ qop: 'auth',
+ realm: 'users@mydomain.com',
+ usernameField: 'email',
+ },
+ function (username, done) {
+ users.findOneAndUpdate(username, function (err, user) {
+ if (err) { return done(err); }
+ if (!user) {
+ return done(null, false);
+ } else {
+ return done(null, user, user.password);
+ }
+ });
+ },
+ function (params, done) {
+ process.nextTick(function () {
+ return done(null, true);
+ });
+ }
+ ));
+
+References
+----------
+http://www.sitepoint.com/understanding-http-digest-access-authentication/
+http://en.wikipedia.org/wiki/Digest_access_authentication#Alternative_authentication_protocols
+http://codingsmackdown.tv/blog/2013/01/02/using-response-interceptors-to-show-and-hide-a-loading-widget/
+https://github.com/phpmasterdotcom/UnderstandingHTTPDigest/blob/master/client.php
+
+Acknowledgements
+----------------
+Thanks to Jared Hanson (@jaredhanson) for PassportJS and the [passport-http authentication strategy](https://github.com/jaredhanson/passport-http) and to Greg Pipe (@grevory) for the [AngularJS LocalStorage Service](https://github.com/grevory/angular-local-storage).
+
+License
+-------
+[The MIT License](http://opensource.org/licenses/MIT)
+
+Copyright (c) 2013 Patrick Heneise (@patrickheneise) <[http://patrickheneise.com/](http://patrickheneise.com/)>
\ No newline at end of file
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..e69de29
diff --git a/digest.js b/digest.js
new file mode 100644
index 0000000..d714348
--- /dev/null
+++ b/digest.js
@@ -0,0 +1,101 @@
+'use strict';
+
+angular.module('DigestAuthInterceptor', ['LocalStorageModule'])
+
+.config(function ($locationProvider, $provide) {
+ $provide.factory('digestAuthInterceptor', function ($q, $injector, $location, localStorageService) {
+ return {
+ responseError: function (rejection) {
+ function genNonce(b) {
+ var c = [],
+ e = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
+ a = e.length;
+ for (var d = 0; d < b; ++d) {
+ c.push(e[Math.random() * a | 0]);
+ }
+ return c.join('');
+ }
+ function unq(quotedString) {
+ return quotedString.substr(1, quotedString.length - 2).replace(/(?:(?:\r\n)?[ \t])+/g, ' ');
+ }
+
+ if (rejection.status === 401) {
+ var
+ $http = $injector.get('$http'),
+ email = localStorageService.get('email'),
+ password = localStorageService.get('password'),
+ HA1 = localStorageService.get('authorization'),
+ cnonce = genNonce(10),
+ reg = /.+?\:\/\/.+?(\/.+?)(?:#|\?|$)/,
+ nc = 1,
+ nonce, realm, qop;
+
+ if ((email && password) || HA1) {
+ var
+ ws = '(?:(?:\\r\\n)?[ \\t])+',
+ token = '(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2E\\x30-\\x39\\x3F\\x41-\\x5A\\x5E-\\x7A\\x7C\\x7E]+)',
+ quotedString = '"(?:[\\x00-\\x0B\\x0D-\\x21\\x23-\\x5B\\\\x5D-\\x7F]|' + ws + '|\\\\[\\x00-\\x7F])*"',
+ tokenizer = new RegExp(token + '(?:=(?:' + quotedString + '|' + token + '))?', 'g'),
+ tokens = rejection.headers('WWW-Authenticate').match(tokenizer),
+ uri = reg.exec(rejection.config.url);
+
+ tokens.forEach(function (value) {
+ if (value.match('nonce')) {
+ nonce = unq(value.split('=')[1]);
+ }
+ if (value.match('realm')) {
+ realm = unq(value.split('=')[1]);
+ }
+ if (value.match('qop')) {
+ qop = unq(value.split('=')[1]);
+ }
+ });
+
+ HA1 = HA1 ? HA1 : md5(email + ':' + realm + ':' + password);
+ var
+ HA2 = md5(rejection.config.method + ':' + uri[1]),
+ response = md5(HA1 + ':' + nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + HA2),
+ header = 'Digest username="' + email + '", realm="' + realm +
+ '", nonce="' + nonce + '", uri="' + uri[1] +
+ '", cnonce="' + cnonce + '", nc="' + nc +
+ '", qop="' + qop + '", response="' + response + '"';
+
+ $http.defaults.headers.common.authorization = header;
+
+ $http({
+ method: rejection.config.method,
+ url: rejection.config.url,
+ crossDomain: true,
+ contentType : 'application/json',
+ headers: {
+ 'Authorization': header
+ }
+ })
+ .success(function () {
+ localStorageService.set('authorization', HA1);
+ localStorageService.remove('password');
+
+ var nextUrl = $location.search();
+ if (nextUrl.next) {
+ $location.search('');
+ $location.path(nextUrl.next);
+ } else {
+ $location.path('/');
+ }
+ })
+ .error(function () {
+ localStorageService.clearAll();
+ if ($location.path() !== '/login') {
+ $location.search('next', $location.path());
+ }
+ $location.path('/login');
+ });
+ } else {
+ $location.path('/login');
+ }
+ }
+ return $q.reject(rejection);
+ }
+ };
+ });
+});
diff --git a/example/.bowerrc b/example/.bowerrc
new file mode 100644
index 0000000..ba0accc
--- /dev/null
+++ b/example/.bowerrc
@@ -0,0 +1,3 @@
+{
+ "directory": "app/bower_components"
+}
diff --git a/example/.gitignore b/example/.gitignore
new file mode 100644
index 0000000..7911b28
--- /dev/null
+++ b/example/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+dist
+.tmp
+.sass-cache
+app/bower_components
diff --git a/example/.jshintrc b/example/.jshintrc
new file mode 100644
index 0000000..40377ba
--- /dev/null
+++ b/example/.jshintrc
@@ -0,0 +1,24 @@
+{
+ "node": true,
+ "browser": true,
+ "esnext": true,
+ "bitwise": true,
+ "camelcase": true,
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "indent": 2,
+ "latedef": true,
+ "newcap": true,
+ "noarg": true,
+ "quotmark": "single",
+ "regexp": true,
+ "undef": true,
+ "unused": true,
+ "strict": true,
+ "trailing": true,
+ "smarttabs": true,
+ "globals": {
+ "angular": false
+ }
+}
diff --git a/example/.travis.yml b/example/.travis.yml
new file mode 100644
index 0000000..83f4e22
--- /dev/null
+++ b/example/.travis.yml
@@ -0,0 +1,7 @@
+language: node_js
+node_js:
+ - '0.8'
+ - '0.10'
+before_script:
+ - 'npm install -g bower grunt-cli'
+ - 'bower install'
diff --git a/example/Gruntfile.js b/example/Gruntfile.js
new file mode 100644
index 0000000..4b83746
--- /dev/null
+++ b/example/Gruntfile.js
@@ -0,0 +1,327 @@
+// Generated on 2013-09-10 using generator-angular 0.4.0
+'use strict';
+var LIVERELOAD_PORT = 35729;
+var lrSnippet = require('connect-livereload')({ port: LIVERELOAD_PORT });
+var mountFolder = function (connect, dir) {
+ return connect.static(require('path').resolve(dir));
+};
+
+// # Globbing
+// for performance reasons we're only matching one level down:
+// 'test/spec/{,*/}*.js'
+// use this if you want to recursively match all subfolders:
+// 'test/spec/**/*.js'
+
+module.exports = function (grunt) {
+ require('load-grunt-tasks')(grunt);
+ require('time-grunt')(grunt);
+
+ // configurable paths
+ var yeomanConfig = {
+ app: 'app',
+ dist: 'dist'
+ };
+
+ try {
+ yeomanConfig.app = require('./bower.json').appPath || yeomanConfig.app;
+ } catch (e) {}
+
+ grunt.initConfig({
+ yeoman: yeomanConfig,
+ watch: {
+ styles: {
+ files: ['<%= yeoman.app %>/styles/{,*/}*.css'],
+ tasks: ['copy:styles', 'autoprefixer']
+ },
+ livereload: {
+ options: {
+ livereload: LIVERELOAD_PORT
+ },
+ files: [
+ '<%= yeoman.app %>/{,*/}*.html',
+ '.tmp/styles/{,*/}*.css',
+ '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
+ '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
+ ]
+ }
+ },
+ autoprefixer: {
+ options: ['last 1 version'],
+ dist: {
+ files: [{
+ expand: true,
+ cwd: '.tmp/styles/',
+ src: '{,*/}*.css',
+ dest: '.tmp/styles/'
+ }]
+ }
+ },
+ connect: {
+ options: {
+ port: 9000,
+ // Change this to '0.0.0.0' to access the server from outside.
+ hostname: 'localhost'
+ },
+ livereload: {
+ options: {
+ middleware: function (connect) {
+ return [
+ lrSnippet,
+ mountFolder(connect, '.tmp'),
+ mountFolder(connect, yeomanConfig.app)
+ ];
+ }
+ }
+ },
+ test: {
+ options: {
+ middleware: function (connect) {
+ return [
+ mountFolder(connect, '.tmp'),
+ mountFolder(connect, 'test')
+ ];
+ }
+ }
+ },
+ dist: {
+ options: {
+ middleware: function (connect) {
+ return [
+ mountFolder(connect, yeomanConfig.dist)
+ ];
+ }
+ }
+ }
+ },
+ open: {
+ server: {
+ url: 'http://localhost:<%= connect.options.port %>'
+ }
+ },
+ clean: {
+ dist: {
+ files: [{
+ dot: true,
+ src: [
+ '.tmp',
+ '<%= yeoman.dist %>/*',
+ '!<%= yeoman.dist %>/.git*'
+ ]
+ }]
+ },
+ server: '.tmp'
+ },
+ jshint: {
+ options: {
+ jshintrc: '.jshintrc'
+ },
+ all: [
+ 'Gruntfile.js',
+ '<%= yeoman.app %>/scripts/{,*/}*.js'
+ ]
+ },
+ // not used since Uglify task does concat,
+ // but still available if needed
+ /*concat: {
+ dist: {}
+ },*/
+ rev: {
+ dist: {
+ files: {
+ src: [
+ '<%= yeoman.dist %>/scripts/{,*/}*.js',
+ '<%= yeoman.dist %>/styles/{,*/}*.css',
+ '<%= yeoman.dist %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
+ '<%= yeoman.dist %>/styles/fonts/*'
+ ]
+ }
+ }
+ },
+ useminPrepare: {
+ html: '<%= yeoman.app %>/index.html',
+ options: {
+ dest: '<%= yeoman.dist %>'
+ }
+ },
+ usemin: {
+ html: ['<%= yeoman.dist %>/{,*/}*.html'],
+ css: ['<%= yeoman.dist %>/styles/{,*/}*.css'],
+ options: {
+ dirs: ['<%= yeoman.dist %>']
+ }
+ },
+ imagemin: {
+ dist: {
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.app %>/images',
+ src: '{,*/}*.{png,jpg,jpeg}',
+ dest: '<%= yeoman.dist %>/images'
+ }]
+ }
+ },
+ svgmin: {
+ dist: {
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.app %>/images',
+ src: '{,*/}*.svg',
+ dest: '<%= yeoman.dist %>/images'
+ }]
+ }
+ },
+ cssmin: {
+ // By default, your `index.html` will take care of
+ // minification. This option is pre-configured if you do not wish to use
+ // Usemin blocks.
+ // dist: {
+ // files: {
+ // '<%= yeoman.dist %>/styles/main.css': [
+ // '.tmp/styles/{,*/}*.css',
+ // '<%= yeoman.app %>/styles/{,*/}*.css'
+ // ]
+ // }
+ // }
+ },
+ htmlmin: {
+ dist: {
+ options: {
+ /*removeCommentsFromCDATA: true,
+ // https://github.com/yeoman/grunt-usemin/issues/44
+ //collapseWhitespace: true,
+ collapseBooleanAttributes: true,
+ removeAttributeQuotes: true,
+ removeRedundantAttributes: true,
+ useShortDoctype: true,
+ removeEmptyAttributes: true,
+ removeOptionalTags: true*/
+ },
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.app %>',
+ src: ['*.html', 'views/*.html'],
+ dest: '<%= yeoman.dist %>'
+ }]
+ }
+ },
+ // Put files not handled in other tasks here
+ copy: {
+ dist: {
+ files: [{
+ expand: true,
+ dot: true,
+ cwd: '<%= yeoman.app %>',
+ dest: '<%= yeoman.dist %>',
+ src: [
+ '*.{ico,png,txt}',
+ '.htaccess',
+ 'bower_components/**/*',
+ 'images/{,*/}*.{gif,webp}',
+ 'styles/fonts/*'
+ ]
+ }, {
+ expand: true,
+ cwd: '.tmp/images',
+ dest: '<%= yeoman.dist %>/images',
+ src: [
+ 'generated/*'
+ ]
+ }]
+ },
+ styles: {
+ expand: true,
+ cwd: '<%= yeoman.app %>/styles',
+ dest: '.tmp/styles/',
+ src: '{,*/}*.css'
+ }
+ },
+ concurrent: {
+ server: [
+ 'copy:styles'
+ ],
+ test: [
+ 'copy:styles'
+ ],
+ dist: [
+ 'copy:styles',
+ 'imagemin',
+ 'svgmin',
+ 'htmlmin'
+ ]
+ },
+ karma: {
+ unit: {
+ configFile: 'karma.conf.js',
+ singleRun: true
+ }
+ },
+ cdnify: {
+ dist: {
+ html: ['<%= yeoman.dist %>/*.html']
+ }
+ },
+ ngmin: {
+ dist: {
+ files: [{
+ expand: true,
+ cwd: '<%= yeoman.dist %>/scripts',
+ src: '*.js',
+ dest: '<%= yeoman.dist %>/scripts'
+ }]
+ }
+ },
+ uglify: {
+ dist: {
+ files: {
+ '<%= yeoman.dist %>/scripts/scripts.js': [
+ '<%= yeoman.dist %>/scripts/scripts.js'
+ ]
+ }
+ }
+ }
+ });
+
+ grunt.registerTask('server', function (target) {
+ if (target === 'dist') {
+ return grunt.task.run(['build', 'open', 'connect:dist:keepalive']);
+ }
+
+ grunt.task.run([
+ 'clean:server',
+ 'concurrent:server',
+ 'autoprefixer',
+ 'connect:livereload',
+ 'open',
+ 'watch'
+ ]);
+ });
+
+ grunt.registerTask('test', [
+ 'clean:server',
+ 'concurrent:test',
+ 'autoprefixer',
+ 'connect:test',
+ 'karma'
+ ]);
+
+ grunt.registerTask('build', [
+ 'clean:dist',
+ 'useminPrepare',
+ 'concurrent:dist',
+ 'autoprefixer',
+ 'concat',
+ 'copy:dist',
+ 'cdnify',
+ 'ngmin',
+ 'cssmin',
+ 'uglify',
+ 'rev',
+ 'usemin'
+ ]);
+
+ grunt.registerTask('default', [
+ 'jshint',
+ 'test',
+ 'build'
+ ]);
+};
diff --git a/example/app/.buildignore b/example/app/.buildignore
new file mode 100644
index 0000000..fc98b8e
--- /dev/null
+++ b/example/app/.buildignore
@@ -0,0 +1 @@
+*.coffee
\ No newline at end of file
diff --git a/example/app/.htaccess b/example/app/.htaccess
new file mode 100644
index 0000000..cb84cb9
--- /dev/null
+++ b/example/app/.htaccess
@@ -0,0 +1,543 @@
+# Apache Configuration File
+
+# (!) Using `.htaccess` files slows down Apache, therefore, if you have access
+# to the main server config file (usually called `httpd.conf`), you should add
+# this logic there: http://httpd.apache.org/docs/current/howto/htaccess.html.
+
+# ##############################################################################
+# # CROSS-ORIGIN RESOURCE SHARING (CORS) #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Cross-domain AJAX requests |
+# ------------------------------------------------------------------------------
+
+# Enable cross-origin AJAX requests.
+# http://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity
+# http://enable-cors.org/
+
+#
+# Header set Access-Control-Allow-Origin "*"
+#
+
+# ------------------------------------------------------------------------------
+# | CORS-enabled images |
+# ------------------------------------------------------------------------------
+
+# Send the CORS header for images when browsers request it.
+# https://developer.mozilla.org/en/CORS_Enabled_Image
+# http://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
+# http://hacks.mozilla.org/2011/11/using-cors-to-load-webgl-textures-from-cross-domain-images/
+
+
+
+
+ SetEnvIf Origin ":" IS_CORS
+ Header set Access-Control-Allow-Origin "*" env=IS_CORS
+
+
+
+
+# ------------------------------------------------------------------------------
+# | Web fonts access |
+# ------------------------------------------------------------------------------
+
+# Allow access from all domains for web fonts
+
+
+
+ Header set Access-Control-Allow-Origin "*"
+
+
+
+
+# ##############################################################################
+# # ERRORS #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | 404 error prevention for non-existing redirected folders |
+# ------------------------------------------------------------------------------
+
+# Prevent Apache from returning a 404 error for a rewrite if a directory
+# with the same name does not exist.
+# http://httpd.apache.org/docs/current/content-negotiation.html#multiviews
+# http://www.webmasterworld.com/apache/3808792.htm
+
+Options -MultiViews
+
+# ------------------------------------------------------------------------------
+# | Custom error messages / pages |
+# ------------------------------------------------------------------------------
+
+# You can customize what Apache returns to the client in case of an error (see
+# http://httpd.apache.org/docs/current/mod/core.html#errordocument), e.g.:
+
+ErrorDocument 404 /404.html
+
+
+# ##############################################################################
+# # INTERNET EXPLORER #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Better website experience |
+# ------------------------------------------------------------------------------
+
+# Force IE to render pages in the highest available mode in the various
+# cases when it may not: http://hsivonen.iki.fi/doctype/ie-mode.pdf.
+
+
+ Header set X-UA-Compatible "IE=edge"
+ # `mod_headers` can't match based on the content-type, however, we only
+ # want to send this header for HTML pages and not for the other resources
+
+ Header unset X-UA-Compatible
+
+
+
+# ------------------------------------------------------------------------------
+# | Cookie setting from iframes |
+# ------------------------------------------------------------------------------
+
+# Allow cookies to be set from iframes in IE.
+
+#
+# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
+#
+
+# ------------------------------------------------------------------------------
+# | Screen flicker |
+# ------------------------------------------------------------------------------
+
+# Stop screen flicker in IE on CSS rollovers (this only works in
+# combination with the `ExpiresByType` directives for images from below).
+
+# BrowserMatch "MSIE" brokenvary=1
+# BrowserMatch "Mozilla/4.[0-9]{2}" brokenvary=1
+# BrowserMatch "Opera" !brokenvary
+# SetEnvIf brokenvary 1 force-no-vary
+
+
+# ##############################################################################
+# # MIME TYPES AND ENCODING #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Proper MIME types for all files |
+# ------------------------------------------------------------------------------
+
+
+
+ # Audio
+ AddType audio/mp4 m4a f4a f4b
+ AddType audio/ogg oga ogg
+
+ # JavaScript
+ # Normalize to standard type (it's sniffed in IE anyways):
+ # http://tools.ietf.org/html/rfc4329#section-7.2
+ AddType application/javascript js jsonp
+ AddType application/json json
+
+ # Video
+ AddType video/mp4 mp4 m4v f4v f4p
+ AddType video/ogg ogv
+ AddType video/webm webm
+ AddType video/x-flv flv
+
+ # Web fonts
+ AddType application/font-woff woff
+ AddType application/vnd.ms-fontobject eot
+
+ # Browsers usually ignore the font MIME types and sniff the content,
+ # however, Chrome shows a warning if other MIME types are used for the
+ # following fonts.
+ AddType application/x-font-ttf ttc ttf
+ AddType font/opentype otf
+
+ # Make SVGZ fonts work on iPad:
+ # https://twitter.com/FontSquirrel/status/14855840545
+ AddType image/svg+xml svg svgz
+ AddEncoding gzip svgz
+
+ # Other
+ AddType application/octet-stream safariextz
+ AddType application/x-chrome-extension crx
+ AddType application/x-opera-extension oex
+ AddType application/x-shockwave-flash swf
+ AddType application/x-web-app-manifest+json webapp
+ AddType application/x-xpinstall xpi
+ AddType application/xml atom rdf rss xml
+ AddType image/webp webp
+ AddType image/x-icon ico
+ AddType text/cache-manifest appcache manifest
+ AddType text/vtt vtt
+ AddType text/x-component htc
+ AddType text/x-vcard vcf
+
+
+
+# ------------------------------------------------------------------------------
+# | UTF-8 encoding |
+# ------------------------------------------------------------------------------
+
+# Use UTF-8 encoding for anything served as `text/html` or `text/plain`.
+AddDefaultCharset utf-8
+
+# Force UTF-8 for certain file formats.
+
+ AddCharset utf-8 .atom .css .js .json .rss .vtt .webapp .xml
+
+
+
+# ##############################################################################
+# # URL REWRITES #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Rewrite engine |
+# ------------------------------------------------------------------------------
+
+# Turning on the rewrite engine and enabling the `FollowSymLinks` option is
+# necessary for the following directives to work.
+
+# If your web host doesn't allow the `FollowSymlinks` option, you may need to
+# comment it out and use `Options +SymLinksIfOwnerMatch` but, be aware of the
+# performance impact: http://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
+
+# Also, some cloud hosting services require `RewriteBase` to be set:
+# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-mod-rewrite-not-working-on-my-site
+
+
+ Options +FollowSymlinks
+ # Options +SymLinksIfOwnerMatch
+ RewriteEngine On
+ # RewriteBase /
+
+
+# ------------------------------------------------------------------------------
+# | Suppressing / Forcing the "www." at the beginning of URLs |
+# ------------------------------------------------------------------------------
+
+# The same content should never be available under two different URLs especially
+# not with and without "www." at the beginning. This can cause SEO problems
+# (duplicate content), therefore, you should choose one of the alternatives and
+# redirect the other one.
+
+# By default option 1 (no "www.") is activated:
+# http://no-www.org/faq.php?q=class_b
+
+# If you'd prefer to use option 2, just comment out all the lines from option 1
+# and uncomment the ones from option 2.
+
+# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Option 1: rewrite www.example.com → example.com
+
+
+ RewriteCond %{HTTPS} !=on
+ RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
+ RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Option 2: rewrite example.com → www.example.com
+
+# Be aware that the following might not be a good idea if you use "real"
+# subdomains for certain parts of your website.
+
+#
+# RewriteCond %{HTTPS} !=on
+# RewriteCond %{HTTP_HOST} !^www\..+$ [NC]
+# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
+#
+
+
+# ##############################################################################
+# # SECURITY #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Content Security Policy (CSP) |
+# ------------------------------------------------------------------------------
+
+# You can mitigate the risk of cross-site scripting and other content-injection
+# attacks by setting a Content Security Policy which whitelists trusted sources
+# of content for your site.
+
+# The example header below allows ONLY scripts that are loaded from the current
+# site's origin (no inline scripts, no CDN, etc). This almost certainly won't
+# work as-is for your site!
+
+# To get all the details you'll need to craft a reasonable policy for your site,
+# read: http://html5rocks.com/en/tutorials/security/content-security-policy (or
+# see the specification: http://w3.org/TR/CSP).
+
+#
+# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
+#
+# Header unset Content-Security-Policy
+#
+#
+
+# ------------------------------------------------------------------------------
+# | File access |
+# ------------------------------------------------------------------------------
+
+# Block access to directories without a default document.
+# Usually you should leave this uncommented because you shouldn't allow anyone
+# to surf through every directory on your server (which may includes rather
+# private places like the CMS's directories).
+
+
+ Options -Indexes
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Block access to hidden files and directories.
+# This includes directories used by version control systems such as Git and SVN.
+
+
+ RewriteCond %{SCRIPT_FILENAME} -d [OR]
+ RewriteCond %{SCRIPT_FILENAME} -f
+ RewriteRule "(^|/)\." - [F]
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Block access to backup and source files.
+# These files may be left by some text editors and can pose a great security
+# danger when anyone has access to them.
+
+
+ Order allow,deny
+ Deny from all
+ Satisfy All
+
+
+# ------------------------------------------------------------------------------
+# | Secure Sockets Layer (SSL) |
+# ------------------------------------------------------------------------------
+
+# Rewrite secure requests properly to prevent SSL certificate warnings, e.g.:
+# prevent `https://www.example.com` when your certificate only allows
+# `https://secure.example.com`.
+
+#
+# RewriteCond %{SERVER_PORT} !^443
+# RewriteRule ^ https://example-domain-please-change-me.com%{REQUEST_URI} [R=301,L]
+#
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+# Force client-side SSL redirection.
+
+# If a user types "example.com" in his browser, the above rule will redirect him
+# to the secure version of the site. That still leaves a window of opportunity
+# (the initial HTTP connection) for an attacker to downgrade or redirect the
+# request. The following header ensures that browser will ONLY connect to your
+# server via HTTPS, regardless of what the users type in the address bar.
+# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
+
+#
+# Header set Strict-Transport-Security max-age=16070400;
+#
+
+# ------------------------------------------------------------------------------
+# | Server software information |
+# ------------------------------------------------------------------------------
+
+# Avoid displaying the exact Apache version number, the description of the
+# generic OS-type and the information about Apache's compiled-in modules.
+
+# ADD THIS DIRECTIVE IN THE `httpd.conf` AS IT WILL NOT WORK IN THE `.htaccess`!
+
+# ServerTokens Prod
+
+
+# ##############################################################################
+# # WEB PERFORMANCE #
+# ##############################################################################
+
+# ------------------------------------------------------------------------------
+# | Compression |
+# ------------------------------------------------------------------------------
+
+
+
+ # Force compression for mangled headers.
+ # http://developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping
+
+
+ SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
+ RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
+
+
+
+ # Compress all output labeled with one of the following MIME-types
+ # (for Apache versions below 2.3.7, you don't need to enable `mod_filter`
+ # and can remove the `` and `` lines
+ # as `AddOutputFilterByType` is still in the core directives).
+
+ AddOutputFilterByType DEFLATE application/atom+xml \
+ application/javascript \
+ application/json \
+ application/rss+xml \
+ application/vnd.ms-fontobject \
+ application/x-font-ttf \
+ application/x-web-app-manifest+json \
+ application/xhtml+xml \
+ application/xml \
+ font/opentype \
+ image/svg+xml \
+ image/x-icon \
+ text/css \
+ text/html \
+ text/plain \
+ text/x-component \
+ text/xml
+
+
+
+
+# ------------------------------------------------------------------------------
+# | Content transformations |
+# ------------------------------------------------------------------------------
+
+# Prevent some of the mobile network providers from modifying the content of
+# your site: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9.5.
+
+#
+# Header set Cache-Control "no-transform"
+#
+
+# ------------------------------------------------------------------------------
+# | ETag removal |
+# ------------------------------------------------------------------------------
+
+# Since we're sending far-future expires headers (see below), ETags can
+# be removed: http://developer.yahoo.com/performance/rules.html#etags.
+
+# `FileETag None` is not enough for every server.
+
+ Header unset ETag
+
+
+FileETag None
+
+# ------------------------------------------------------------------------------
+# | Expires headers (for better cache control) |
+# ------------------------------------------------------------------------------
+
+# The following expires headers are set pretty far in the future. If you don't
+# control versioning with filename-based cache busting, consider lowering the
+# cache time for resources like CSS and JS to something like 1 week.
+
+
+
+ ExpiresActive on
+ ExpiresDefault "access plus 1 month"
+
+ # CSS
+ ExpiresByType text/css "access plus 1 year"
+
+ # Data interchange
+ ExpiresByType application/json "access plus 0 seconds"
+ ExpiresByType application/xml "access plus 0 seconds"
+ ExpiresByType text/xml "access plus 0 seconds"
+
+ # Favicon (cannot be renamed!)
+ ExpiresByType image/x-icon "access plus 1 week"
+
+ # HTML components (HTCs)
+ ExpiresByType text/x-component "access plus 1 month"
+
+ # HTML
+ ExpiresByType text/html "access plus 0 seconds"
+
+ # JavaScript
+ ExpiresByType application/javascript "access plus 1 year"
+
+ # Manifest files
+ ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
+ ExpiresByType text/cache-manifest "access plus 0 seconds"
+
+ # Media
+ ExpiresByType audio/ogg "access plus 1 month"
+ ExpiresByType image/gif "access plus 1 month"
+ ExpiresByType image/jpeg "access plus 1 month"
+ ExpiresByType image/png "access plus 1 month"
+ ExpiresByType video/mp4 "access plus 1 month"
+ ExpiresByType video/ogg "access plus 1 month"
+ ExpiresByType video/webm "access plus 1 month"
+
+ # Web feeds
+ ExpiresByType application/atom+xml "access plus 1 hour"
+ ExpiresByType application/rss+xml "access plus 1 hour"
+
+ # Web fonts
+ ExpiresByType application/font-woff "access plus 1 month"
+ ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
+ ExpiresByType application/x-font-ttf "access plus 1 month"
+ ExpiresByType font/opentype "access plus 1 month"
+ ExpiresByType image/svg+xml "access plus 1 month"
+
+
+
+# ------------------------------------------------------------------------------
+# | Filename-based cache busting |
+# ------------------------------------------------------------------------------
+
+# If you're not using a build process to manage your filename version revving,
+# you might want to consider enabling the following directives to route all
+# requests such as `/css/style.12345.css` to `/css/style.css`.
+
+# To understand why this is important and a better idea than `*.css?v231`, read:
+# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring
+
+#
+# RewriteCond %{REQUEST_FILENAME} !-f
+# RewriteCond %{REQUEST_FILENAME} !-d
+# RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
+#
+
+# ------------------------------------------------------------------------------
+# | File concatenation |
+# ------------------------------------------------------------------------------
+
+# Allow concatenation from within specific CSS and JS files, e.g.:
+# Inside of `script.combined.js` you could have
+#
+#
+# and they would be included into this single file.
+
+#
+#
+# Options +Includes
+# AddOutputFilterByType INCLUDES application/javascript application/json
+# SetOutputFilter INCLUDES
+#
+#
+# Options +Includes
+# AddOutputFilterByType INCLUDES text/css
+# SetOutputFilter INCLUDES
+#
+#
+
+# ------------------------------------------------------------------------------
+# | Persistent connections |
+# ------------------------------------------------------------------------------
+
+# Allow multiple requests to be sent over the same TCP connection:
+# http://httpd.apache.org/docs/current/en/mod/core.html#keepalive.
+
+# Enable if you serve a lot of static content but, be aware of the
+# possible disadvantages!
+
+#
+# Header set Connection Keep-Alive
+#
diff --git a/example/app/404.html b/example/app/404.html
new file mode 100644
index 0000000..fdace4a
--- /dev/null
+++ b/example/app/404.html
@@ -0,0 +1,157 @@
+
+
+
+
+ Page Not Found :(
+
+
+
+
+
Not found :(
+
Sorry, but the page you were trying to view does not exist.
+
It looks like this was the result of either:
+
+ - a mistyped address
+ - an out-of-date link
+
+
+
+
+
+
diff --git a/example/app/favicon.ico b/example/app/favicon.ico
new file mode 100644
index 0000000..6527905
Binary files /dev/null and b/example/app/favicon.ico differ
diff --git a/example/app/index.html b/example/app/index.html
new file mode 100644
index 0000000..6eca8c8
--- /dev/null
+++ b/example/app/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/app/robots.txt b/example/app/robots.txt
new file mode 100644
index 0000000..9417495
--- /dev/null
+++ b/example/app/robots.txt
@@ -0,0 +1,3 @@
+# robotstxt.org
+
+User-agent: *
diff --git a/example/app/scripts/app.js b/example/app/scripts/app.js
new file mode 100644
index 0000000..bfd9138
--- /dev/null
+++ b/example/app/scripts/app.js
@@ -0,0 +1,22 @@
+'use strict';
+
+angular.module('exampleApp', [
+ 'ngRoute',
+ 'LocalStorageModule',
+ 'DigestAuthInterceptor',
+ ])
+ .config(function ($routeProvider, $httpProvider) {
+ $routeProvider
+ .when('/', {
+ templateUrl: 'views/main.html',
+ controller: 'MainCtrl'
+ })
+ .when('/login', {
+ templateUrl: 'views/login.html'
+ })
+ .otherwise({
+ redirectTo: '/login'
+ });
+
+ $httpProvider.interceptors.push('digestAuthInterceptor');
+ });
diff --git a/example/app/scripts/controllers/main.js b/example/app/scripts/controllers/main.js
new file mode 100644
index 0000000..c866b18
--- /dev/null
+++ b/example/app/scripts/controllers/main.js
@@ -0,0 +1,25 @@
+'use strict';
+
+angular.module('exampleApp')
+ .controller('MainCtrl', function ($scope, $location, localStorageService) {
+ var auth = localStorageService.get('auth');
+
+ if (!auth) {
+ $location.path('/login');
+ }
+ })
+
+ .controller('LoginFormCtrl', function ($scope, $http, localStorageService) {
+ $scope.submit = function () {
+ localStorageService.add('email', $scope.email);
+ localStorageService.add('password', $scope.password);
+
+ $http.post('http://127.0.0.1:3000/api/login', {
+ headers: {'Content-Type': 'application/json'}
+ }).then(function (response) {
+ console.log('success', response);
+ }, function (response) {
+ console.log('error', response);
+ });
+ };
+ });
\ No newline at end of file
diff --git a/example/app/styles/main.css b/example/app/styles/main.css
new file mode 100644
index 0000000..c754fdd
--- /dev/null
+++ b/example/app/styles/main.css
@@ -0,0 +1,22 @@
+body {
+ background: #fafafa;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ color: #333;
+}
+
+.hero-unit {
+ margin: 50px auto 0 auto;
+ width: 300px;
+ font-size: 18px;
+ font-weight: 200;
+ line-height: 30px;
+ background-color: #eee;
+ border-radius: 6px;
+ padding: 60px;
+}
+
+.hero-unit h1 {
+ font-size: 60px;
+ line-height: 1;
+ letter-spacing: -1px;
+}
diff --git a/example/app/views/login.html b/example/app/views/login.html
new file mode 100644
index 0000000..cfa4f7d
--- /dev/null
+++ b/example/app/views/login.html
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/example/app/views/main.html b/example/app/views/main.html
new file mode 100644
index 0000000..a5a2c45
--- /dev/null
+++ b/example/app/views/main.html
@@ -0,0 +1 @@
+Authenticated.
\ No newline at end of file
diff --git a/example/bower.json b/example/bower.json
new file mode 100644
index 0000000..e58df66
--- /dev/null
+++ b/example/bower.json
@@ -0,0 +1,15 @@
+{
+ "name": "example",
+ "version": "0.0.0",
+ "dependencies": {
+ "angular": "~1.2.0",
+ "angular-localstorage": "*",
+ "angular-digest-interceptor": "~0.0.1",
+ "js-md5": "~1.0.3",
+ "angular-route": "~1.2.0-rc.2"
+ },
+ "devDependencies": {
+ "angular-mocks": "~1.0.7",
+ "angular-scenario": "~1.0.7"
+ }
+}
diff --git a/example/package.json b/example/package.json
new file mode 100644
index 0000000..95dd147
--- /dev/null
+++ b/example/package.json
@@ -0,0 +1,48 @@
+{
+ "name": "example",
+ "version": "0.0.0",
+ "dependencies": {},
+ "devDependencies": {
+ "grunt": "~0.4.1",
+ "grunt-contrib-copy": "~0.4.1",
+ "grunt-contrib-concat": "~0.3.0",
+ "grunt-contrib-uglify": "~0.2.0",
+ "grunt-contrib-compass": "~0.5.0",
+ "grunt-contrib-jshint": "~0.6.0",
+ "grunt-contrib-cssmin": "~0.6.0",
+ "grunt-contrib-connect": "~0.3.0",
+ "grunt-contrib-clean": "~0.5.0",
+ "grunt-contrib-htmlmin": "~0.1.3",
+ "grunt-contrib-imagemin": "git://github.com/PatrickHeneise/grunt-contrib-imagemin.git",
+ "grunt-contrib-watch": "~0.5.2",
+ "grunt-autoprefixer": "~0.2.0",
+ "grunt-usemin": "~0.1.11",
+ "grunt-svgmin": "~0.2.0",
+ "grunt-rev": "~0.1.0",
+ "grunt-open": "~0.2.0",
+ "grunt-concurrent": "~0.3.0",
+ "load-grunt-tasks": "~0.1.0",
+ "connect-livereload": "~0.2.0",
+ "grunt-google-cdn": "~0.2.0",
+ "grunt-ngmin": "~0.0.2",
+ "time-grunt": "~0.1.0",
+ "karma-ng-scenario": "~0.1.0",
+ "grunt-karma": "~0.6.2",
+ "karma-script-launcher": "~0.1.0",
+ "karma-firefox-launcher": "~0.1.0",
+ "karma-chrome-launcher": "~0.1.0",
+ "karma-html2js-preprocessor": "~0.1.0",
+ "karma-jasmine": "~0.1.3",
+ "karma-requirejs": "~0.1.0",
+ "karma-coffee-preprocessor": "~0.1.0",
+ "karma-phantomjs-launcher": "~0.1.0",
+ "karma": "~0.10.2",
+ "karma-ng-html2js-preprocessor": "~0.1.0"
+ },
+ "engines": {
+ "node": ">=0.8.0"
+ },
+ "scripts": {
+ "test": "grunt test"
+ }
+}