diff --git a/.bowerrc b/.bowerrc deleted file mode 100644 index 69fad35..0000000 --- a/.bowerrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "directory": "bower_components" -} diff --git a/.gitignore b/.gitignore index 6c90f96..3a057c4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ -node_modules +app.browser +app.config +bower_components dist +keys +node_modules .tmp .sass-cache authconf.json -bower_components +nvl.log \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 26f185f..7a2bc6b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -16,11 +16,11 @@ module.exports = function (grunt) { require('time-grunt')(grunt); var modRewrite = require('connect-modrewrite'); + var serveStatic = require('serve-static'); // Configurable paths for the application var appConfig = { - app: require('./bower.json').appPath || 'app', - dist: 'dist' + app: 'app', }; var authConfig = @@ -42,18 +42,19 @@ module.exports = function (grunt) { auth_config_data: authConfigData, // Watches files for changes and runs tasks based on the changed files + // to rebundle browserify bundle use watchify of npm... watch: { livereload: { options: { livereload: '<%= connect.options.livereload %>' }, files: [ - '<%= yeoman.app %>/{,*/}*.html', - '<%= yeoman.app %>/{,*/}*.js', - '.tmp/styles/{,*/}*.css', - '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}' + 'app/{,*/}*.html', + 'app/styles/{,*/}*.css', + 'app/scripts/{,*/}*.js', + 'app.config/{,*/}*.js' ], - tasks: ['newer:copy'] + tasks: ['newer:copy:browser'] } }, @@ -61,39 +62,65 @@ module.exports = function (grunt) { connect: { options: { port: "<%= auth_config_data.APP_PORT %>", // may need to convert to number? + livereload: 35729, // Change this to '0.0.0.0' to access the server from outside. hostname: '<%= auth_config_data.APP_HOST %>', - livereload: 35729 + open: false, + base: [ + 'app.config', + 'app.browser', + 'app', + ], + middleware: function (connect, options) { + var middlewares = []; + middlewares.push(connect().use( + '/node_modules', + serveStatic('./node_modules') + )); + middlewares.push(modRewrite(['^[^\\.]*$ /index.html [L]'])); + middlewares.push(connect().use( + '/bower_components', + serveStatic('./bower_components') + )); + options.base.forEach(function (base) { + return middlewares.push(serveStatic(base)); + }); + return middlewares; + }, }, livereload: { options: { open: true, + } + }, + dist: { + options: { + livereload: false, base: [ - '.tmp', - 'dist/app' + 'dist' ], - middleware: function (connect, options) { - var middlewares = []; - middlewares.push(modRewrite(['^[^\\.]*$ /index.html [L]'])); - middlewares.push(connect().use( - '/bower_components', - connect.static('./bower_components') - )); - options.base.forEach(function (base) { - return middlewares.push(connect['static'](base)); - }); - return middlewares; - } + keepalive: true, + } }, }, // Empties folders to start fresh clean: { - server: { - files: [{ - src: ['.tmp', 'dist'] - }] + config: { + files: [{ + src: ['app.config'] + }] + }, + browser: { + files: [{ + src: ['app.browser'] + }] + }, + dist: { + files: [{ + src: ['dist'] + }] } }, @@ -102,64 +129,183 @@ module.exports = function (grunt) { copy: { styles: { expand: true, - cwd: '<%= yeoman.app %>/styles', - dest: '.tmp/styles/', + cwd: 'app/styles', src: '{,*/}*.css' }, - dist: { - expand: true, - dest: 'dist', - src: [ - 'register_with_anvil_connect.sh', - '<%= yeoman.app %>/**'], + config: { options: { - process: function( content, srcpath) { + process: function (content, srcpath) { return grunt.template.process( content, {data: authConfigData}); }, }, + expand: true, + cwd: 'app.config.templ', + dest: 'app.config', + src: [ + 'anvil-config.js', + 'register_with_anvil_connect.sh' + ] + }, + browser: { + options: { + process: function (content, srcpath) { + return grunt.template.process( + content, + {data: authConfigData}); + }, + }, + files: { + 'app.browser/index.html': ['app/index.html'] + } + }, + distApp: { + expand: true, + cwd: 'app', + dest: 'dist', + src: ['**/*'], + }, + distBrowser: { + expand: true, + cwd: 'app.browser', + dest: 'dist', + src: ['**/*'], }, }, - // Run some tasks in parallel to speed up the build process - concurrent: { - server: [ - 'copy:styles', - 'copy:dist', - ] + browserify: { + options: { + browserifyOptions: { + debug: true + } + }, + browser: { + files: { + 'app.browser/scripts/dev-bundle.js': + ['app/scripts/app.js'], + 'app.browser/scripts/rp-dev-bundle.js': + ['app/scripts/rp.js'], + 'app.browser/scripts/popup-dev-bundle.js': + ['app/scripts/callback_popup.js'] + } + }, + dist: { + options: { + browserifyOptions: { + debug: false + } + }, + files: { + 'dist/scripts/app-bundle.js': ['dist/scripts/app.js'], + 'dist/scripts/rp-bundle.js': ['dist/scripts/rp.js'], + 'dist/scripts/popup-bundle.js': ['dist/scripts/callback_popup.js'] + } + } }, + uglify: { + options: { + mangle: false + }, + dist: { + files: { + 'dist/scripts/app-bundle.min.js': [ + 'node_modules/es5-shim/es5-shim.js', + 'node_modules/json3/lib/json3.min.js', + 'node_modules/promiz/promiz.js', + 'node_modules/webcrypto-shim/webcrypto-shim.js', + 'node_modules/text-encoder-lite/index.js', + 'dist/scripts/app-bundle.js' + ], + 'dist/scripts/rp-bundle.min.js': [ + 'node_modules/es5-shim/es5-shim.js', + 'node_modules/json3/lib/json3.min.js', + 'node_modules/promiz/promiz.js', + 'node_modules/webcrypto-shim/webcrypto-shim.js', + 'node_modules/text-encoder-lite/index.js', + 'dist/scripts/rp-bundle.js' + ], + 'dist/scripts/popup-bundle.min.js': [ + 'node_modules/es5-shim/es5-shim.js', + 'node_modules/json3/lib/json3.min.js', + 'node_modules/promiz/promiz.js', + 'node_modules/webcrypto-shim/webcrypto-shim.js', + 'node_modules/text-encoder-lite/index.js', + 'dist/scripts/popup-bundle.js' + ] + } + } + }, + + processhtml: { + dist: { + files: { + 'dist/index.html': ['app.browser/index.html'], + 'dist/rp.html': ['app.browser/rp.html'], + 'dist/callback_popup.html': ['app.browser/callback_popup.html'] + } + } + } }); - grunt.registerTask('chmodScript', 'Makes script executable', function(target) { + grunt.registerTask('chmodScript', '(internal) Makes script executable. Used by config task', function(target) { var fs = require('fs'); - fs.chmodSync('dist/register_with_anvil_connect.sh', '755'); + fs.chmodSync('app.config/register_with_anvil_connect.sh', '755'); }); - grunt.registerTask('build', function (target) { - grunt.log.writeln('Build app in dist folder, matching auth server configuration in %s', grunt.config('auth_config')); - grunt.log.writeln('If not yet done register client using dist/register_with_anvil_connect.sh. See README.md'); + + grunt.registerTask('build_browser', '(internal) Builds app in app.browser. Used by serve task.', function (target) { + grunt.log.writeln('Build app scripts in app.browser folder, matching auth server configuration in %s', grunt.config('auth_config')); grunt.task.run([ - 'clean', - 'copy:dist', - 'chmodScript', + 'clean:browser', + 'copy:browser', + 'browserify:browser' ]); }); + grunt.registerTask('config', 'Primary task: Generates config in app.config based on authconf.json', function (target) { + grunt.log.writeln('Generating config in app.config folder, matching auth server configuration in %s', grunt.config('auth_config')); + grunt.log.writeln('If not yet done register client using app.config/register_with_anvil_connect.sh. See README.md'); + grunt.task.run([ + 'clean:config', + 'copy:config', + 'chmodScript', + ]); + }); - grunt.registerTask('serve', 'Compile then start a connect web server', function (target) { + grunt.registerTask('serve', 'Primary task: Build then start a connect web server\n (serve for livereload app or serve:dist) ', function (target) { if (target === 'dist') { - return grunt.task.run(['build', 'connect:dist:keepalive']); + return grunt.task.run(['build', 'connect:dist']); } + grunt.log.writeln('Builds app, starts livereload server and opens browser.'); + grunt.log.writeln('NOTE: also start `npm run watchify` to rebuild the browserify bundles on changes.'); + grunt.task.run([ - 'clean:server', - 'concurrent:server', + 'build_browser', 'connect:livereload', 'watch' ]); }); + grunt.registerTask('build', 'Builds app in /dist folder', function (target) { + grunt.log.writeln('** Build app in dist folder, matching auth server configuration in %s', grunt.config('auth_config')); + grunt.task.run([ + 'build_browser', + 'clean:dist', + 'copy:distApp', + 'copy:distBrowser', + 'browserify:dist', + 'uglify:dist', + 'processhtml:dist' + ]); + }); + + grunt.registerTask('serve_dist', 'Starts server on /dist', function (target) { + grunt.task.run([ + 'connect:dist' + ]); + }); }; diff --git a/README.md b/README.md index ec6348d..241f2a5 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,101 @@ This repository demonstrates authenticating users of an AngularJS app using Anvil Connect. ## Prerequisites + +### Setup server: Setup [Anvil Connect authorization server](https://github.com/anvilresearch/connect/blob/master/README.md) -As a result the Anvil Authorization server should run on localhost:3000. +These instructions assume that the Anvil Authorization server (issuer) runs on localhost:3000 in development mode (not using docker). However it should also be adaptable to using docker. + +One must register the application with the issuer. For this the cli is used. The [cli must be setup](https://github.com/anvilresearch/connect-docs/blob/master/cli.md) first so that you can login to the server. + +### Get connect-js client libraries + +There is currently work in progress to update the client libraries to use +webcrypto APIs instead of encryption libraries. See +[Webcrypto API · Issue #7 · anvilresearch/connect-js](https://github.com/anvilresearch/connect-js/issues/7) for more details. + +To get the webcrypto code for testing the fork is used: + +```console +$ # create or got to some suited directory then +$ git clone https://github.com/anvilresearch/connect-js.git +$ cd connect-js +$ git checkout webcrypto-api + +## These steps are described here: https://github.com/anvilresearch/connect-js/tree/webcrypto-api +$ npm install +$ npm run test +``` + +Next get this project and npm link it with the connect-js library installed +in the previous step + +```console +$ # create or got to some suited directory. This could be the same as above. +$ git clone https://github.com/anvilresearch/connect-example-angularjs.git +$ cd connect-example-angularjs +$ git checkout henrjk-use-npm +``` + +Now go to the directory where connect-js was cloned to and issue these commands: +```console +connect-js dev (webcrypto-api)$ npm link + +> anvil-connect-js@0.2.6 postinstall /Users/dev/code/connect-js +> jspm install + + Looking up npm:babel-runtime +... about 40 more lines of output ... +ok Up to date - babel-runtime as npm:babel-runtime@^5.8.24 (5.8.34) +ok Install tree has no forks. + +ok Install complete. + +> anvil-connect-js@0.2.6 prepublish /Users/dev/code/connect-js +> npm run build + + +> anvil-connect-js@0.2.6 build /Users/dev/code/connect-js +> grunt + +Running "clean:dist" (clean) task +>> 24 paths cleaned. + +Running "babel:compile" (babel) task + +Done, without errors. +/Users/dev/.nvm/versions/node/v0.12.6/lib/node_modules/anvil-connect-js -> /Users/dev/code/connect-js + +/connect-js dev (webcrypto-api)$ +``` +Then go back to the `connect-example-angularjs` directory and do the following: + +```console +connect-js dev (webcrypto-api)$ cd ../connect-example-angularjs/ +connect-example-angularjs dev (henrjk-use-npm)$ npm link anvil-connect-js +/Users/dev/code/work/connect-js-fork/connect-example-angularjs/node_modules/anvil-connect-js -> /Users/dev/.nvm/versions/node/v0.12.6/lib/node_modules/anvil-connect-js -> /Users/dev/code/work/connect-js-fork/connect-js + +connect-example-angularjs dev (use-npm)$ npm install +# this takes a few minutes... +``` + +Now you will have to come up with a authconf.json file in the projects +root folder which matches your server. This is described below. These +steps looks still valid but have not been recently tried. +Here is an outline of the steps: + +* Determine correct `authconf.json` file and place it in connect-example-angularjs root folder. +* Generate configuration files based on authconf.json: `grunt config` +* login to the server with `nvl login` +* execute script generated by grunt build: `./app.config/register_with_anvil_connect.sh` +* Observe the assigned client ID and update `authconf.json` accordingly. +* Start the app in dev mode via `grunt serve` + +This should build the browser app configured correctly for the client registration +in folder app.browser, browserify the dependencies and the open a browser +showing the  + ## Register the client and generate matching angular sources. To allow the app to connect to the authorization server a client (representing) @@ -22,13 +114,14 @@ To achieve this the configuration information for the client is stored in file You will find that this file is **not** checked in. Instead there are files with similar names which are checked in. These are good starting points for your -client registration. For example they differ -depending on whether boot2docker is involved or not. Also there can be +client registration. They differ +depending on whether there is a distinct docker host which is *not* localhost or +not. Also there are differences on how the authentication is displayed, for example using a popup or a new page. Use one of these as a starting point and copy them to `authconf.json`. For -example when using docker via boot2docker the following is a good starting +example when running using docker via boot2docker the following is a good starting point: ```console @@ -41,39 +134,45 @@ via `grunt serve` then use: cp authconf.dev.localhost.json authconf.json ``` - The Anvil Authentication server recognizes clients by an id which is generated when they are registered. Therefore the starting point is not yet final as the -id in there must be changed. To help with ensure that the client registration +id in there must be changed. To help to ensure that the client registration matches what the generated angular app uses, a registration script is generated -in `dist/register_with_anvil_connect.sh`. +in `app.config/register_with_anvil_connect.sh`. -First generate the script by: +First generate the configuration files based on authconf.json: ```console -grunt build +grunt config ``` -Use the generated `dist/register_with_anvil_connect.sh` as follows in the root directory of your Anvil Connect Authentication -server: +Next login to the cli. ```console -mac:anvil-connect dev$ /Users/dev/code/connect-example-angularjs/dist/register_with_anvil_connect.sh -Registring nv add client { - "client_name": "Angular Example App", - "default_max_age": 36000, - "redirect_uris": [ - "http://localhost:9000/callback_popup.html", - "http://localhost:9000/callback_page.html", - "http://localhost:9000/rp.html"], - "post_logout_redirect_uris": ["http://localhost:9000"], - "trusted": "true" -} +dev$ nvl login +? Select an Anvil Connect instance localhost:3000 (localhost-3000) +Selected issuer localhost:3000 (http://localhost:3000) +Warning: you are communicating over plain text. +? Enter your email example@gmail.com +? Enter your password ************ +You have been successfully logged in to localhost:3000 +``` + +After the nvl login one can use the generated +`dist/register_with_anvil_connect.sh` to register your app with the client. +However you may want to review the script and change argument values for +--name and perhaps --logo-uri. + +To register the client: + +```console +dev$ ./config/register_with_anvil_connect.sh +Registering this client with localhost-3000 Succeeded. Define CLIENT_ID as follows in authconf.json: { ... - "CLIENT_ID" : "d20dc3cf-cdfd-4e14-a070-0cb40bdb5d92", + "CLIENT_ID" : "29dcbf2a-88d6-4038-b9f9-6bf425104b59", ... } ``` @@ -81,53 +180,58 @@ Define CLIENT_ID as follows in authconf.json: The id shown will be unique to your authentication server. Replace the existing `CLIENT_ID` in `authconf.json` with the one you see in your output. +When you build the app the values in authConfig will be used to ensure that the +generated app uses matching configuration values. For js files which +require file app.config/anvil-config.js the config will be incorporated in the +bundles produced by browserify. + ## Run with angular app served by grunt serve -In this scenaria we are using a simple build server via grunt. +In this scenario we are using a simple development server via grunt. + ```console -igelmac:connect-example-angularjs dev$ grunt serve +dev$ grunt serve Using authconf.json Running "serve" task +Builds app, starts livereload server and opens browser. +NOTE: also start `npm run watchify` to rebuild the browserify bundles on changes. -Running "clean:server" (clean) task -Cleaning .tmp...OK -Cleaning dist...OK - -Running "concurrent:server" (concurrent) task - - Using authconf.json +Running "build_browser" task +Build app scripts in app.browser folder, matching auth server configuration in authconf.json - Running "copy:styles" (copy) task - Copied 1 files +Running "clean:browser" (clean) task +>> 1 path cleaned. - Done, without errors. +Running "copy:browser" (copy) task +Copied 1 file +Running "browserify:browser" (browserify) task +>> Bundle app.browser/scripts/popup-dev-bundle.js created. +>> Bundle app.browser/scripts/rp-dev-bundle.js created. +>> Bundle app.browser/scripts/dev-bundle.js created. - Execution Time (2015-07-10 19:49:12 UTC) - loading tasks 20ms ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 31% - copy:styles 41ms ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 63% - Total 65ms - - Using authconf.json +Running "connect:livereload" (connect) task +Started connect web server on http://localhost:9000 - Running "copy:dist" (copy) task - Created 5 directories, copied 10 files +Running "watch" task +Waiting... +>> File "app.config/anvil-config.js" changed. +Using authconf.json - Done, without errors. +Running "newer:copy:browser" (newer) task +No newer files to process. +Done, without errors. - Execution Time (2015-07-10 19:49:12 UTC) - loading tasks 18ms ▇▇▇▇▇ 9% - copy:dist 183ms ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 91% - Total 202ms -Running "connect:livereload" (connect) task -Started connect web server on http://localhost:9000 +Execution Time (2016-02-03 11:38:31 UTC) +loading tasks 12ms ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 43% +newer:copy:browser 15ms ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 54% +Total 28ms -Running "watch" task -Waiting... +Completed in 2.858s at Wed Feb 03 2016 12:38:31 GMT+0100 (CET) - Waiting... ``` As a result the browser should open the home page like this: @@ -136,10 +240,17 @@ As a result the browser should open the home page like this: When changes are made to the app this should readily refresh the browser. +However for the Browserify bundles to be rebuild on changes you need to also run: +``` +npm run watchify +``` + These pages should look the same as in the docker use case which shows some more pages except that these are running under `http://localhost:9000` instead of `http://boot2docker:9000`. ## Run with angular app served by docker +**Note:** This section was last updated in July 2015. + In this scenario nginx serves the angular app running inside a docker container. If you have not followed the steps in section **Prerequisites** do this first. diff --git a/app.config.templ/anvil-config.js b/app.config.templ/anvil-config.js new file mode 100644 index 0000000..b8a0a86 --- /dev/null +++ b/app.config.templ/anvil-config.js @@ -0,0 +1,7 @@ +module.exports = { + issuer: '<%=AUTH_SERVER%>', + client_id: '<%=CLIENT_ID%>', + app_server: '<%=APP_SERVER%>', + display: '<%=AUTH_DISPLAY%>', + callback: '<%=APP_AUTH_CALLBACK%>' +} \ No newline at end of file diff --git a/app.config.templ/register_with_anvil_connect.sh b/app.config.templ/register_with_anvil_connect.sh new file mode 100755 index 0000000..a2deac6 --- /dev/null +++ b/app.config.templ/register_with_anvil_connect.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# This is a template which is expanded and stored in dist when building +# the app with grunt. + +# Register client with anvil connect + +fail() { + local message + message=${1-"Failed to register client"} + echo "$message" >&2 + echo "You may need to login with 'nvl login'" >&2 + echo "Perhaps nvl is not yet setup? Consult connect-cli getting started documentation" >&2 + echo "" >&2 + echo "For other problems consult the output of the nvl command in nvl.log" >&2 + exit 2 +} + +echo "Registering this client with <%= ISSUER_NAME %>" +echo "Command output written to nvl.log" + +nvl client:register \ + --issuer "<%= ISSUER_NAME %>" \ + --trusted \ + --name "Angular example with <%= AUTH_DISPLAY %> for <%= APP_SERVER %>" \ + --uri "<%= APP_SERVER %>" \ + --logo-uri "<%= APP_SERVER %>/logo" \ + --application-type "web" \ + --response-type "id_token token" \ + --grant-type "implicit" \ + --default-max-age "3600" \ + --redirect-uri "<%= APP_SERVER %>/<%= APP_AUTH_CALLBACK %>" \ + --redirect-uri "<%= APP_SERVER %>/rp.html" \ + --post-logout-redirect-uri "<%= APP_SERVER %>/" \ + --post-logout-redirect-uri "<%= APP_SERVER %>/" > nvl.log 2>&1 + + +# duplication of --post-logout-redirect-uri see https://github.com/anvilresearch/connect-cli/issues/70 + +REGISTER_STATUS=$? +if [ $REGISTER_STATUS -ne 0 ]; then + fail "nvl client:register failed with status $REGISTER_STATUS" +fi + +# From OS X: +# $ echo "$out" | grep "_id" | grep -o -E '([[:xdigit:]]*-){1,10}[[:xdigit:]]*' +# ec8262ae-28d5-4943-8237-d8145042c3e0 +client_id=$(cat nvl.log | grep "_id" | grep -o -E '([[:xdigit:]]*-){1,10}[[:xdigit:]]*') + +[ -n "$client_id" ] || fail + +echo "Succeeded." +echo "Define CLIENT_ID as follows in authconf.json:" +printf '{\n' +printf '...\n' +printf ' "CLIENT_ID" : "%s",\n' "$client_id" +printf '...\n' +printf '}\n' diff --git a/app/callback_popup.html b/app/callback_popup.html index 2621167..57e4f03 100644 --- a/app/callback_popup.html +++ b/app/callback_popup.html @@ -1,24 +1,29 @@
- + + - console.log("load callback") - console.log("pageUrl=" , pageUrl); - console.log("pageOrigin=" , pageOrigin); - console.log("opener=" , opener); - opener.postMessage(pageUrl, pageOrigin); - window.close(); - }); - + + + + + -Please close this window.
+