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:

+ + + +
+ + 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" + } +}