From c377a86455a2677eee3f463082c1c3cf34d3f00c Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Wed, 22 Jun 2016 01:12:11 -0400 Subject: [PATCH 01/16] Added fakesp to the project. Put localtunnel in its own folder. Created ez_dev script. --- .gitignore | 1 + Gruntfile.js | 2 +- ez_dev.sh | 91 +++++++++++++++++ fakesp/README.md | 59 +++++++++++ fakesp/package.json | 22 ++++ fakesp/server.js | 193 +++++++++++++++++++++++++++++++++++ fakesp/views/error.jade | 8 ++ fakesp/views/index.jade | 45 ++++++++ fakesp/views/login-form.jade | 67 ++++++++++++ localtunnel/localtunnel.js | 124 ++++++++++++++++++++++ localtunnel/package.json | 13 +++ package.json | 1 - 12 files changed, 624 insertions(+), 2 deletions(-) create mode 100755 ez_dev.sh create mode 100644 fakesp/README.md create mode 100644 fakesp/package.json create mode 100644 fakesp/server.js create mode 100644 fakesp/views/error.jade create mode 100644 fakesp/views/index.jade create mode 100644 fakesp/views/login-form.jade create mode 100644 localtunnel/localtunnel.js create mode 100644 localtunnel/package.json diff --git a/.gitignore b/.gitignore index 0760e7c..cafdfd0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist app/bower_components coverage .env +*.log diff --git a/Gruntfile.js b/Gruntfile.js index ef0d491..795fdfe 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -80,7 +80,7 @@ module.exports = function (grunt) { }, livereload: { options: { - open: 'http://<%=hostname %>:<%=port %>', + //open: 'http://<%=hostname %>:<%=port %>', base: [ '.tmp', '<%= yeoman.app %>' diff --git a/ez_dev.sh b/ez_dev.sh new file mode 100755 index 0000000..a8fe9da --- /dev/null +++ b/ez_dev.sh @@ -0,0 +1,91 @@ +#! /bin/bash + +echo "You'll need to know your Stormpath api key id, api key secret and application href in order to proceed" +echo + +echo "Setting up..." +echo + +echo "Executing: npm install..." +npm install + +if [ $? -eq 0 ] +then + echo "Successfully ran: npm install" +else + echo "npm install failed" +fi + +echo "Executing: bower install..." +bower install + +if [ $? -eq 0 ] +then + echo "Successfully ran: bower install" +else + echo "bower install failed" +fi + +echo "Setting up localtunnel..." +cd localtunnel +npm install + +if [ $? -eq 0 ] +then + echo "Successfully setup localtunnel" +else + echo "Failed to setup localtunnel" +fi + +cd .. + +echo "Setting up fakesp..." +cd fakesp +npm install + +if [ $? -eq 0 ] +then + echo "Successfully setup fakesp" +else + echo "Failed to setup fakesp" +fi + +cd .. +echo + +read -e -p "Enter your Stormpath API Key ID and press [ENTER]: " STORMPATH_CLIENT_APIKEY_ID +[ -z "${STORMPATH_CLIENT_APIKEY_ID}" ] && echo "Must specify a Stormpath API Key ID" && exit 1 + +read -e -p "Enter your Stormpath API Key Secret and press [ENTER]: " STORMPATH_CLIENT_APIKEY_SECRET +[ -z "${STORMPATH_CLIENT_APIKEY_SECRET}" ] && echo "Must specify a Stormpath API Key Secret" && exit 1 + +read -e -p "Enter your Stormpath Application HREF and press [ENTER]: " STORMPATH_APPLICATION_HREF +[ -z "${STORMPATH_APPLICATION_HREF}" ] && echo "Must specify a Stormpath Application HREF" && exit 1 + +read -e -p "Enter local domain and press [ENTER] (default: localhost): " DOMAIN +[ -z "${DOMAIN}" ] && DOMAIN=localhost + +export STORMPATH_CLIENT_APIKEY_ID STORMPATH_CLIENT_APIKEY_SECRET STORMPATH_APPLICATION_HREF DOMAIN + +echo +echo "Running fakesp..." +cd fakesp +EXPR="node server.js > ../fakesp.log 2>&1 &" +eval $EXPR +FAKESP_PID=$! +cd .. + +echo "Running localtunnel..." +cd localtunnel +EXPR="node localtunnel.js > ../localtunnel.log 2>&1 &" +eval $EXPR +LOCALTUNNEL_PID=$! +cd .. + +echo "Launching browser..." +sleep 1 +open http://localhost:8001 + +echo +echo "Running grunt serve..." +grunt serve diff --git a/fakesp/README.md b/fakesp/README.md new file mode 100644 index 0000000..dcec848 --- /dev/null +++ b/fakesp/README.md @@ -0,0 +1,59 @@ +### Fake service provider + +Use this to imitate a service-provider initiated login flow for the +Stormpath ID Site Feature. The repo contains a simple HTTP server +that serves a webapp which will redirect you to ID site for authentication. +When you return from ID site, a local cookie-based session will be created +for you. This is different from the SSO session which ID site retains +for you. + +### Installation + +`git clone` this repo, then `cd` into the folder + +`npm install` inside the folder + +### Configuration - Your Dev Machine + +Point `stormpath.localhost` to `127.0.0.1` by putting an entry into `/etc/hosts` + +Export the configuration for a Stormpath Application, with API keys, to your environment: + + export STORMPATH_CLIENT_APIKEY_ID=XXX + export STORMPATH_CLIENT_APIKEY_SECRET=XXX + export STORMPATH_APPLICATION_HREF=https://api.stormpath.com/v1/applications/XXX + +### Configuration - Your Stormpath Tenant + +Login to your Stormpath Tenant and add the following URLs to the list of +"Authorized Redirect URLs" in your ID Site configuration: + + http://stormpath.localhost:8001/idSiteCallback + +### Start the Server + +You can run the server with default options like so: + +``` +node server.js +``` + +Or with options: + +``` +ID_SITE_PATH= \ +PORT=8001 \ +DOMAIN=stormpath.localhost \ +CB_URI=http://stormpath.localhost:8001/idSiteCallback \ +node server.js +``` + +If the server starts sucessfully, you will see this message in your console: + +``` +Starting server on port 8001 +Server running, open this URL in your browser: +http://stormpath.localhost:8001 +``` + +You can now use the application by navigating to http://stormpath.localhost:8001 \ No newline at end of file diff --git a/fakesp/package.json b/fakesp/package.json new file mode 100644 index 0000000..f851751 --- /dev/null +++ b/fakesp/package.json @@ -0,0 +1,22 @@ +{ + "name": "fakesp", + "version": "0.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "client-sessions": "^0.7.0", + "express": "^4.9.6", + "jade": "^1.7.0", + "njwt": "^0.2.2", + "node-uuid": "^1.4.1", + "open": "0.0.5", + "request": "^2.70.0", + "stormpath": "^0.15.4" + } +} diff --git a/fakesp/server.js b/fakesp/server.js new file mode 100644 index 0000000..63a49c8 --- /dev/null +++ b/fakesp/server.js @@ -0,0 +1,193 @@ +var express = require('express'); +var nJwt = require('njwt'); +var sessions = require('client-sessions'); +var stormpath = require('stormpath'); +var request = require('request'); +var url = require('url'); + +var IS_PRODUCTION = process.env.NODE_ENV==='production'; +var PORT = process.env.PORT || 8001; +var DOMAIN = process.env.DOMAIN || 'stormpath.localhost'; +var ID_SITE_PATH = process.env.ID_SITE_PATH || ''; +var CB_URI = process.env.CB_URI || ('http://' + DOMAIN + ':' + PORT + '/idSiteCallback' ); + +var app = express(); +var application; +var client = new stormpath.Client(); + +app.set('views', './views'); +app.set('view engine', 'jade'); + + +var spCookieInterface = sessions({ + cookieName: 'sp', // cookie name dictates the key name added to the request object + secret: 'will be set after client initialization', // should be a large unguessable string + duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms + activeDuration: 1000 * 60 * 5 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds +}); + +var lastJwtCookieInterface = sessions({ + cookieName: 'lastJwt', // cookie name dictates the key name added to the request object + secret: 'will be set after client initialization', // should be a large unguessable string + duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms + activeDuration: 1000 * 60 * 5 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds +}); + +var idSiteBaseUrlCookieInterface = sessions({ + cookieName: 'idSiteBaseUrl', // cookie name dictates the key name added to the request object + secret: 'will be set after client initialization', // should be a large unguessable string + duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms + activeDuration: 1000 * 60 * 5 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds +}); + +app.use(lastJwtCookieInterface); +app.use(spCookieInterface); +app.use(idSiteBaseUrlCookieInterface); + +app.get('/', function(req, res){ + if(req.sp && req.sp.accountHref){ + client.getAccount(req.sp.accountHref,function(err,account){ + if(err){ + res.render('error',{ + errorText: String(err) + }); + }else{ + res.render('index',{ + lastJwt: req.lastJwt.value ? JSON.stringify(req.lastJwt.value,null,2) : null, + account: account, + accountJson: JSON.stringify(account,null,2), + cb_uri: CB_URI, + idSiteBaseUrl: req.idSiteBaseUrl.value + }); + } + }); + }else{ + + res.render('index',{ + lastJwt: req.lastJwt.value ? JSON.stringify(req.lastJwt.value,null,2) : null, + account: null, + cb_uri: CB_URI, + idSiteBaseUrl: req.idSiteBaseUrl.value + }); + + } +}); + +app.get('/idSiteCallback',function(req,res){ + if(req.query.jwtResponse){ + application.handleIdSiteCallback(req.url,function(err,idSiteResult){ + if(err){ + res.render('error',{ + errorText: JSON.stringify(err) + }); + }else{ + if(idSiteResult.status !== 'LOGOUT'){ + req.sp.accountHref = idSiteResult.account.href; + } + req.lastJwt.value = nJwt.verify(req.query.jwtResponse,client.config.client.apiKey.secret); + res.redirect('/'); + } + }); + } +}); + + +app.get('/login', function(req, res){ + + var options = { + callbackUri: req.query.cb_uri || CB_URI, + path: ID_SITE_PATH + }; + + if(req.query.sof){ + options.showOrganizationField = Boolean(parseInt(req.query.sof,10)); + } + + if(req.query.onk){ + options.organizationNameKey = req.query.onk; + } + + if(req.query.usd){ + options.useSubDomain = true; + } + + if(req.query.state){ + options.state = req.query.state; + } + + if(req.query.path){ + options.path = req.query.path; + } + var ssoRequestUrl = application.createIdSiteUrl(options); + + if(req.query.idSiteBaseUrl) { + var parsedIdSiteBaseUrl = url.parse(req.query.idSiteBaseUrl); + var idSiteBaseUrl = parsedIdSiteBaseUrl.protocol + '//' + parsedIdSiteBaseUrl.host; + req.idSiteBaseUrl.value = idSiteBaseUrl; + + request({ + method: 'GET', + url: ssoRequestUrl, + followRedirect: false + }, function (err, response, body) { + var idSiteRedirectUrl = response.headers.location || ''; + if (err) { + res.json(err); + } else if(response.statusCode !== 302) { + res.json(body); + } else if(idSiteRedirectUrl.match(/jwtResponse/)) { + res.redirect(response.location); + } else if(req.query.idSiteBaseUrl) { + var parts = idSiteRedirectUrl.split('/#'); + var newUrl = idSiteBaseUrl + '/#' + parts[1]; + res.setHeader('Location', newUrl); + res.status(302); + res.end(); + } else { + res.redirect(idSiteRedirectUrl); + } + }); + } else { + req.idSiteBaseUrl.value = ''; + res.redirect(ssoRequestUrl); + } + +}); + +app.get('/logout', function(req, res){ + req.sp.destroy(); + res.redirect(application.createIdSiteUrl({ + callbackUri: CB_URI, + logout: true + })); +}); + + + + +function startServer(){ + lastJwtCookieInterface.secret = client.config.apiKey.secret; + spCookieInterface.secret = client.config.apiKey.secret; + console.log('Starting server on port ' + PORT); + app.listen(PORT,function(){ + console.log('Server running, open this URL in your browser:'); + console.log('http://'+DOMAIN+':'+PORT); + }); + +} + +function getApplication(then){ + client.getApplication(client.config.application.href,function(err,a){ + if (err){ + throw err; + } + application = a; + then(); + }); +} + + +client.on('ready',function(){ + getApplication(startServer); +}); + diff --git a/fakesp/views/error.jade b/fakesp/views/error.jade new file mode 100644 index 0000000..82b84d3 --- /dev/null +++ b/fakesp/views/error.jade @@ -0,0 +1,8 @@ +html + head + link(rel='stylesheet', type='text/css', href='//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css') + body + pre.alert.alert-danger=errorText + p   + center + a.btn.btn-primary.btn-lg(href="/") Return to home page \ No newline at end of file diff --git a/fakesp/views/index.jade b/fakesp/views/index.jade new file mode 100644 index 0000000..0ca3a78 --- /dev/null +++ b/fakesp/views/index.jade @@ -0,0 +1,45 @@ +html + head + link(rel="stylesheet", type="text/css", href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css") + body + div.container + if !account + p   + p   + include login-form + + + if account + h2 You are loggen in + hr + p + | You have a cookie-based session, tied to this server. You can now do one of the following: + table.table.table-bordered + tr + td + p + | If your SSO cookie is still valid you will be immediately redirected back here. + | If your SSO cookie has expired you will be presented with the ID Site login application + include login-form + + tr + td + p + | This will send you to the ID Site with a logout request. + | You will return to this application and your session will be destroyed + div.panel.panel-default + div.panel-heading Logout Request + div.panel-body + center + a.btn.btn-primary(href='/logout') Logout + .panel.panel-default + .panel-heading + h3.panel-title Account of current session: + .panel-body + pre=accountJson + if lastJwt + div.panel.panel-default + div.panel-heading + h3.panel-title Last seen JWT: + div.panel-body + pre=lastJwt \ No newline at end of file diff --git a/fakesp/views/login-form.jade b/fakesp/views/login-form.jade new file mode 100644 index 0000000..c4f3def --- /dev/null +++ b/fakesp/views/login-form.jade @@ -0,0 +1,67 @@ +script(type="application/javascript"). + function revertIdSiteBaseUrl() { + var idSiteBaseUrlInupt = document.getElementById('idSiteBaseUrl'); + idSiteBaseUrlInupt.value = ''; + } +form.form-horizontal(action="/login",method="get") + div.panel.panel-default + div.panel-heading + h3.panel-title Login Request + div.panel-body + div.form-group + label.control-label.col-xs-4 Show Organization Field + div.col-xs-6.col-sm-3.col-md-2 + div.radio + label + input(type="radio" name="sof" checked value="") + span Undefined + div.radio + label + input(type="radio" name="sof" value="1") + span True + div.radio + label + input(type="radio" name="sof" value="0") + span False + + div.form-group + label.control-label.col-xs-4 Organization Name Key + div.col-xs-6.col-sm-3.col-md-2 + input.form-control(type="text" name="onk") + + div.form-group + label.control-label.col-xs-4 Use Subdomain + div.col-xs-6.col-sm-3.col-md-2 + div.checkbox + label + input(type="checkbox" name="usd") + + div.form-group + label.control-label.col-xs-4 State + div.col-xs-6.col-sm-3.col-md-2 + input.form-control(type="text" name="state") + + div.form-group + label.control-label.col-xs-4 Path + div.col-xs-6.col-sm-3.col-md-2 + input.form-control(type="text" name="path") + + div.form-group + label.control-label.col-xs-4 Callback URI + div.col-xs-6.col-sm-3.col-md-5 + input.form-control(type="text" name="cb_uri" value=cb_uri) + + div.form-group + label.control-label.col-xs-4 Alternate ID Site Base URL + div.col-xs-6.col-sm-3.col-md-5 + div.input-group + input.form-control(type="text" name="idSiteBaseUrl" id="idSiteBaseUrl" value=idSiteBaseUrl) + + div.input-group-addon(style="cursor:pointer;" onclick="revertIdSiteBaseUrl(this)") + i.icon.glyphicon.glyphicon-trash + span.help-block Use for local ID Site development. Note: will not persist SSO cookie. + + div.form-group + label.control-label.col-xs-4 + div.col-xs-6.col-sm-3.col-md-2 + button.btn.btn-primary.form-control(type="submit") Log me in! \ No newline at end of file diff --git a/localtunnel/localtunnel.js b/localtunnel/localtunnel.js new file mode 100644 index 0000000..d0932e1 --- /dev/null +++ b/localtunnel/localtunnel.js @@ -0,0 +1,124 @@ +var localtunnel = require('localtunnel'); +var stormpath = require('stormpath'); + +var callbckUri = 'http://stormpath.localhost:8001/idSiteCallback'; +var client = new stormpath.Client(); +var doCleanup = false; +var previousDomainName = null; +var host = null; + +/*eslint no-console: 0*/ + +function prepeareIdSiteModel(client, idSiteApplicationHost, callbckUri, cb) { + client.getCurrentTenant(function(err, tenant) { + if (err) { + throw err; + } + + client.getResource(tenant.href + '/idSites', function(err, collection) { + if (err) { + throw err; + } + + var idSiteModel = collection.items[0]; + + previousDomainName = idSiteModel.domainName; + + idSiteModel.domainName = idSiteApplicationHost.split('//')[1]; + + if (idSiteModel.authorizedOriginUris.indexOf(idSiteApplicationHost) === -1) { + idSiteModel.authorizedOriginUris.push(idSiteApplicationHost); + idSiteModel.authorizedOriginUris.push(idSiteApplicationHost.replace('https','http')); + } + + if (idSiteModel.authorizedRedirectUris.indexOf(callbckUri) === -1) { + idSiteModel.authorizedRedirectUris.push(callbckUri); + } + + idSiteModel.save(cb); + }); + }); +} + +function revertIdSiteModel(client, idSiteApplicationHost, callbckUri, cb) { + client.getCurrentTenant(function(err, tenant) { + if (err) { + throw err; + } + + client.getResource(tenant.href + '/idSites', function(err, collection) { + if (err) { + throw err; + } + + var idSiteModel = collection.items[0]; + + idSiteModel.domainName = previousDomainName; + + idSiteModel.authorizedOriginUris = idSiteModel.authorizedOriginUris.filter(function(uri) { + return !uri.match(idSiteApplicationHost); + }); + + idSiteModel.authorizedOriginUris = idSiteModel.authorizedOriginUris.filter(function(uri) { + return !uri.match(idSiteApplicationHost.replace('https','http')); + }); + + idSiteModel.authorizedRedirectUris = idSiteModel.authorizedRedirectUris.filter(function(uri) { + return !uri.match(callbckUri); + }); + + idSiteModel.save(cb); + }); + }); +} + +function cleanup(cb){ + if(doCleanup){ + revertIdSiteModel(client,host,callbckUri,function (err) { + if (err) { + throw err; + } + console.log('ID Site Model Restored'); + cb(); + }); + } +} + +var tunnel = localtunnel(process.env.PORT || 9000, function(err, tunnel) { + if (err) { + console.error(err); + return process.exit(1); + } + host = tunnel.url; + console.log(host); + prepeareIdSiteModel(client,host,callbckUri,function(err){ + if (err) { + throw err; + } + console.log('ID Site Model Ready'); + setInterval(function () { },1000); + doCleanup = true; + }); + +}); + +tunnel.on('error', function (err) { + console.error(err); + cleanup(); +}); + +tunnel.on('close', cleanup); + +process.on('SIGTERM', function() { + console.log('\nCaught termination signal'); + cleanup(function (){ + process.exit(); + }); +}); + +process.on('SIGINT', function() { + console.log('\nCaught interrupt signal'); + cleanup(function (){ + process.exit(); + }); +}); diff --git a/localtunnel/package.json b/localtunnel/package.json new file mode 100644 index 0000000..db0c794 --- /dev/null +++ b/localtunnel/package.json @@ -0,0 +1,13 @@ +{ + "name": "stormpathidp", + "version": "0.5.0", + "devDependencies": { + "localtunnel": "^1.7.0", + "stormpath": "^0.18.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "license": "Apache-2.0", + "homepage": "https://github.com/stormpath/idsite-src" +} diff --git a/package.json b/package.json index 381517e..97f4333 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "protractor": "^0.22.0", "q": "^1.4.1", "request": "^2.58.0", - "stormpath": "^0.18.2", "time-grunt": "~0.2.1", "untildify": "^2.0.0" }, From 30d65b7017145a4b36d1bdb3d894a9e2eb60b06b Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Thu, 30 Jun 2016 23:24:31 -0400 Subject: [PATCH 02/16] WIP --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 62530cf..7ceb3f2 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ npm install bower install ``` +![tunnel](https://github.com/stormpath/idsite-src/blob/media/docs_images/idsite_tunnel_dev.png) + ## Setup an HTTPS proxy Because ID Site only works with HTTPS, you will need to setup a local tunnel From c23e6225012edff34c0cae6532b1ed9c2929946f Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Fri, 1 Jul 2016 00:40:36 -0400 Subject: [PATCH 03/16] ensure that DOMAIN setting is used in localtunnel.js. ez_dev.sh will use STORMPATH_API_KEY_FILE if set or will use default apiKey.properties, if found. --- README.md | 67 +++++++++++++++----------------------- ez_dev.sh | 48 +++++++++++++++++---------- fakesp/server.js | 3 +- localtunnel/localtunnel.js | 5 ++- 4 files changed, 61 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 7ceb3f2..0e8321e 100644 --- a/README.md +++ b/README.md @@ -36,64 +36,48 @@ It is assumed that you have the following tools installed on your computer * [Node.JS][] * [Localtunnel.me][] -You should clone this repository and these tasks within the repository: +You should clone this repository and then run this within the repository: ```sh npm install bower install ``` -![tunnel](https://github.com/stormpath/idsite-src/blob/media/docs_images/idsite_tunnel_dev.png) +## Using `ez_dev` -## Setup an HTTPS proxy +Within this repository, there's a script called `ez_dev.sh`. This will setup a complete development environment for you +to be able to work on your ID Site Content. -Because ID Site only works with HTTPS, you will need to setup a local tunnel -which will serve your your local ID Site from a secure connection. With the -local tunnel tool you must do this: +Before we get into running the script, let's take a step back to see what `ez_dev` sets up. -> lt --port 9000 +A typical flow for using ID Site is the following: -It will fetch a URL and tell you something like this: +1. You make a request of the `/login` endpoint of your application in the browser. +2. Your application redirects to Stormapth (api.stormpath.com) +3. Stormpath redirects to the ID Site configured in the Stormpath Admin Console +4. ID Site responds to your browser with the `login` view -> your url is: https://wqdkeiseuj.localtunnel.me +When developing locally, you need an application that will connect to ID Site. And, in particular, ID Site being served +locally from the `idsite-src` repo that you cloned so that you can rapidly make changes and see them in action in real +time. And, there's a requirement that the traffic SSL encrypted. -You must take that URL and configure your ID Site accordingly. Please login -to the [Stormpath Admin Console][] and set these options on your ID Site -Configuration: +The `ez_dev` script sets up the necessary architecture needed. The components are: -| Configuration Option | Should be set to | -|----------------------------------------|-------------------------------------------------------------------------------------| -| **Domain Name** | your local tunnel URL | -| **Authorized Javascript Origin URLs** | your local tunnel URL should be in this list | -| **Authorized Redirect URLs** | the endpoint on your server application which will receive the user after ID site (read below) | +1. `fakesp` - A minimal application that will connect to ID Site +2. `localtunnel.me` - A free service that sets up an HTTPS proxy from the public internet to a locally running server. +3. `grunt serve` - A locally running instance of ID Site. -## Your Service Provider (required) +Once this is all running, you browser will automatically open up to: `http://localhost:8001` and you will be able to see the +updated ID Site content you are working on. Any saved changes to the ID Site content are immediately reflected in your browser +when you go to an ID Site view (such as `/login`). -The application (typically, your server) that sends the user to ID Site is known -as the Service Provider (SP). You send the user to ID Site by constructing a -redirect URL with one of our SDKs. For example, [createIdSiteUrl()][] in our -Node.js SDK. +Here's a visual representation of what is setup and the flow of the requests. -After the user authenticates at ID Site, the user is redirected back to your -application. Your application must have a callback URL which receives the user -and validates the `jwtResponse` parameter in the URL (our SDK does this work -for you). - -If you haven't built your service provider we have a simple service provider -application which you can use for testing purposes, see: [Fake SP][] - - -## Startup - -Once you have setup the environment (the steps above) you are ready to start -the development tasks. Run the following command to start the server: - -> grunt serve - -This will open the application your browser. Because the application does not -have a JWT request, you will see the JWT error. At this point you should use -your service provider to redirect the user to your ID Site. +![tunnel architecture][tunnel_image] +Note: The `ez_dev` script alters the ID Site settings in your Stormpath Admin Console. When you are done working on +ID Site, it is recommended that you go back to your Admin Console and revert the `Domain Name`, +`Authorized Javascript Origin URLs`, and `Authorized Redirect URLs` settings. ## Development Process - Stormpath Tenants @@ -185,3 +169,4 @@ License](http://www.apache.org/licenses/LICENSE-2.0). [Node.JS]: http://nodejs.org [Stormpath Admin Console]: https://api.stormpath.com [Stormpath.js]: https://github.com/stormpath/stormpath.js +[tunnel_image]: https://github.com/stormpath/idsite-src/blob/media/docs_images/idsite_tunnel_dev.png diff --git a/ez_dev.sh b/ez_dev.sh index a8fe9da..4592ecc 100755 --- a/ez_dev.sh +++ b/ez_dev.sh @@ -1,11 +1,38 @@ #! /bin/bash -echo "You'll need to know your Stormpath api key id, api key secret and application href in order to proceed" -echo +function get_property +{ + FILE=$1 + PROPERTY=$2 + sed '/^\#/d' $FILE | grep $PROPERTY | tail -n 1 | cut -d "=" -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} echo "Setting up..." echo +if [ -n "${STORMPATH_API_KEY_FILE}" ]; then + STORMPATH_CLIENT_APIKEY_ID=`get_property ${STORMPATH_API_KEY_FILE} "apiKey.id"` + STORMPATH_CLIENT_APIKEY_SECRET=`get_property ${STORMPATH_API_KEY_FILE} "apiKey.secret"` +elif [ -a ~/.stormpath/apiKey.properties ]; then + STORMPATH_CLIENT_APIKEY_ID=`get_property ~/.stormpath/apiKey.properties "apiKey.id"` + STORMPATH_CLIENT_APIKEY_SECRET=`get_property ~/.stormpath/apiKey.properties "apiKey.secret"` +fi + +[ -z "${STORMPATH_CLIENT_APIKEY_ID}" ] && read -e -p "Enter your Stormpath API Key ID and press [ENTER]: " STORMPATH_CLIENT_APIKEY_ID +[ -z "${STORMPATH_CLIENT_APIKEY_ID}" ] && echo "Must specify a Stormpath API Key ID" && exit 1 + +[ -z "${STORMPATH_CLIENT_APIKEY_SECRET}" ] && read -e -p "Enter your Stormpath API Key Secret and press [ENTER]: " STORMPATH_CLIENT_APIKEY_SECRET +[ -z "${STORMPATH_CLIENT_APIKEY_SECRET}" ] && echo "Must specify a Stormpath API Key Secret" && exit 1 + +[ -z "${STORMPATH_APPLICATION_HREF}" ] && read -e -p "Enter your Stormpath Application HREF and press [ENTER] (default: default Stormpath Application): " STORMPATH_APPLICATION_HREF + +read -e -p "Enter local domain and press [ENTER] (default: localhost): " DOMAIN +[ -z "${DOMAIN}" ] && DOMAIN=localhost + +export STORMPATH_CLIENT_APIKEY_ID STORMPATH_CLIENT_APIKEY_SECRET STORMPATH_APPLICATION_HREF DOMAIN + +echo + echo "Executing: npm install..." npm install @@ -53,21 +80,6 @@ fi cd .. echo -read -e -p "Enter your Stormpath API Key ID and press [ENTER]: " STORMPATH_CLIENT_APIKEY_ID -[ -z "${STORMPATH_CLIENT_APIKEY_ID}" ] && echo "Must specify a Stormpath API Key ID" && exit 1 - -read -e -p "Enter your Stormpath API Key Secret and press [ENTER]: " STORMPATH_CLIENT_APIKEY_SECRET -[ -z "${STORMPATH_CLIENT_APIKEY_SECRET}" ] && echo "Must specify a Stormpath API Key Secret" && exit 1 - -read -e -p "Enter your Stormpath Application HREF and press [ENTER]: " STORMPATH_APPLICATION_HREF -[ -z "${STORMPATH_APPLICATION_HREF}" ] && echo "Must specify a Stormpath Application HREF" && exit 1 - -read -e -p "Enter local domain and press [ENTER] (default: localhost): " DOMAIN -[ -z "${DOMAIN}" ] && DOMAIN=localhost - -export STORMPATH_CLIENT_APIKEY_ID STORMPATH_CLIENT_APIKEY_SECRET STORMPATH_APPLICATION_HREF DOMAIN - -echo echo "Running fakesp..." cd fakesp EXPR="node server.js > ../fakesp.log 2>&1 &" @@ -88,4 +100,4 @@ open http://localhost:8001 echo echo "Running grunt serve..." -grunt serve +grunt serve diff --git a/fakesp/server.js b/fakesp/server.js index 63a49c8..2dd77c7 100644 --- a/fakesp/server.js +++ b/fakesp/server.js @@ -7,7 +7,7 @@ var url = require('url'); var IS_PRODUCTION = process.env.NODE_ENV==='production'; var PORT = process.env.PORT || 8001; -var DOMAIN = process.env.DOMAIN || 'stormpath.localhost'; +var DOMAIN = process.env.DOMAIN || 'localhost'; var ID_SITE_PATH = process.env.ID_SITE_PATH || ''; var CB_URI = process.env.CB_URI || ('http://' + DOMAIN + ':' + PORT + '/idSiteCallback' ); @@ -190,4 +190,3 @@ function getApplication(then){ client.on('ready',function(){ getApplication(startServer); }); - diff --git a/localtunnel/localtunnel.js b/localtunnel/localtunnel.js index d0932e1..7ad77b7 100644 --- a/localtunnel/localtunnel.js +++ b/localtunnel/localtunnel.js @@ -1,7 +1,10 @@ var localtunnel = require('localtunnel'); var stormpath = require('stormpath'); -var callbckUri = 'http://stormpath.localhost:8001/idSiteCallback'; +var DOMAIN = process.env.DOMAIN || 'localhost'; +var PORT = process.env.PORT || 8001; + +var callbckUri = process.env.CB_URI || ('http://' + DOMAIN + ':' + PORT + '/idSiteCallback' ); var client = new stormpath.Client(); var doCleanup = false; var previousDomainName = null; From 60d030c4ecec9430d656d4d5d789e3bd83526259 Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Fri, 1 Jul 2016 00:53:56 -0400 Subject: [PATCH 04/16] ez_dev.sh and README.md updates --- README.md | 38 ++++++++++++++++++++++++++++++++++++-- ez_dev.sh | 1 - 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e8321e..04c1c80 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,13 @@ npm install bower install ``` -## Using `ez_dev` +## `ez_dev` Within this repository, there's a script called `ez_dev.sh`. This will setup a complete development environment for you to be able to work on your ID Site Content. -Before we get into running the script, let's take a step back to see what `ez_dev` sets up. +Before we get into running the script, let's take a step back to see what `ez_dev` sets up. If you want to jump right +in, go to the [running ez_dev](#running-ez_dev) section. A typical flow for using ID Site is the following: @@ -75,6 +76,39 @@ Here's a visual representation of what is setup and the flow of the requests. ![tunnel architecture][tunnel_image] +### Running `ez_dev` + +`ez_dev` needs to know the following: + +1. Your Stormpath API Key ID +2. Your Stormpath API Key secret +3. Your Stormpath Application HREF + +The script will look for some common environment variables and file locations in this order: + +1. If `STORMPATH_API_KEY_FILE` is set, it will get the API Key ID and API Key Secret from that file +2. If `~/.stormpath/apiKey.properties` exists, it will get the API Key ID and API Key Secret from that file +3. If `STORMPATH_APPLICATION_HREF` is set, it will use that value for the Stormpath Application to connect to + +If neither 1. or 2. above is met, you will be asked to provide the API Key ID and the API Key Secret to the script + +If 3. above is not met, you will be asked to provide the Application HREF. *Note*: You can leave this value blank if +you only have the default application (`My Application`) defined in your Stormpath tenant from when it was first setup. +If you have any other Applications defined, then you must specify the Application HREF. + +Here are a few run scenarios: + +``` +./ez_dev.sh # may be asked to provide additional information +``` + +``` +# explicit settings - you will not be asked for any additional information +STORMPATH_API_KEY_FILE=~/.stormpath/apiKey.mytenant.properties \ +STORMPATH_APPLICATION_HREF=https://api.stormpath.com/v1/applications/stormpathidentifier123456 \ +./ez_dev.sh +``` + Note: The `ez_dev` script alters the ID Site settings in your Stormpath Admin Console. When you are done working on ID Site, it is recommended that you go back to your Admin Console and revert the `Domain Name`, `Authorized Javascript Origin URLs`, and `Authorized Redirect URLs` settings. diff --git a/ez_dev.sh b/ez_dev.sh index 4592ecc..5a6415f 100755 --- a/ez_dev.sh +++ b/ez_dev.sh @@ -26,7 +26,6 @@ fi [ -z "${STORMPATH_APPLICATION_HREF}" ] && read -e -p "Enter your Stormpath Application HREF and press [ENTER] (default: default Stormpath Application): " STORMPATH_APPLICATION_HREF -read -e -p "Enter local domain and press [ENTER] (default: localhost): " DOMAIN [ -z "${DOMAIN}" ] && DOMAIN=localhost export STORMPATH_CLIENT_APIKEY_ID STORMPATH_CLIENT_APIKEY_SECRET STORMPATH_APPLICATION_HREF DOMAIN From 574a36b2b3ff9e4e8a4bb73e138e3fb83be5c95c Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Tue, 12 Jul 2016 15:20:12 -0400 Subject: [PATCH 05/16] removed commented out open call. ez_dev.sh handles this now. --- Gruntfile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 795fdfe..0bc4ac0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -80,7 +80,6 @@ module.exports = function (grunt) { }, livereload: { options: { - //open: 'http://<%=hostname %>:<%=port %>', base: [ '.tmp', '<%= yeoman.app %>' From cc6c61424076f3753fdd76ad8c7d192cae14fa77 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 13 Jul 2016 10:45:08 -0700 Subject: [PATCH 06/16] wordsmithing the readme --- README.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 04c1c80..16a63d7 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,19 @@ intuitive API and expert support make it easy for developers to authenticate, manage, and secure users and roles in any application. This is the development environment for the Stormpath hosted ID Site. You can -use this repository to build the same single page application (SPA) that -Stormpath provides by default. The SPA uses Angular and Browserify and it is -built using Grunt and Yeoman. +fork this repository and use it to build the same single page application (SPA) +that Stormpath provides by default, and you can make any changes that you +require for your custom version. -If you want to build your own ID Site and are comfortable with Angular, you should -use this repository. - -If you are not using Angular, you can build your own ID Site from scratch -but you will need to use [Stormpath.js][] in order to communicate with our API -from your custom ID Site application. +This default application is built with Angular and has default views and styling. +If you need to make significant changes that don't fit within this application, +you may want to use [Stormpath.js][] instead. That is a smaller library that +gives you the necessary Stormpath APIs to do user management, but does not come +with pre-built views or styling. ## Browser Support -ID Site will work in the following web browser environments: +This ID Site application will work in the following web browser environments: * Chrome (all versions) * Internet Explorer 10+ @@ -43,28 +42,31 @@ npm install bower install ``` -## `ez_dev` +## Getting started with `ez_dev` Within this repository, there's a script called `ez_dev.sh`. This will setup a complete development environment for you -to be able to work on your ID Site Content. +to be able to work on your ID Site application. Before we get into running the script, let's take a step back to see what `ez_dev` sets up. If you want to jump right in, go to the [running ez_dev](#running-ez_dev) section. A typical flow for using ID Site is the following: -1. You make a request of the `/login` endpoint of your application in the browser. -2. Your application redirects to Stormapth (api.stormpath.com) -3. Stormpath redirects to the ID Site configured in the Stormpath Admin Console -4. ID Site responds to your browser with the `login` view +1. Your make a request of the `/login` endpoint of your application in the browser. +2. Your application redirects the browser to Stormapth (api.stormpath.com/sso). +3. Stormpath redirects the browser to your ID Site, as configured in the Stormpath Admin Console. +4. The ID Site application loads in your browser and presents the login form. +5. After login or registration, your browser is directed back to the server from step 1. + +When customizing ID Site on your local machine, you need: -When developing locally, you need an application that will connect to ID Site. And, in particular, ID Site being served -locally from the `idsite-src` repo that you cloned so that you can rapidly make changes and see them in action in real -time. And, there's a requirement that the traffic SSL encrypted. +* A web server to handle steps 1 and 5 +* A web server to serve the assets at step 4, that would usually be handled by Stormpath. +* SSL encryption for step 3, as required by Stormpath. The `ez_dev` script sets up the necessary architecture needed. The components are: -1. `fakesp` - A minimal application that will connect to ID Site +1. `fakesp` - A web server that will handle steps 1 and 5. 2. `localtunnel.me` - A free service that sets up an HTTPS proxy from the public internet to a locally running server. 3. `grunt serve` - A locally running instance of ID Site. From 3ef18e8cf270f8f223a9dbdd09539c1de8efca91 Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Wed, 22 Jun 2016 01:12:11 -0400 Subject: [PATCH 07/16] Added fakesp to the project. Put localtunnel in its own folder. Created ez_dev script. --- .gitignore | 1 + Gruntfile.js | 2 +- ez_dev.sh | 91 +++++++++++++++++ fakesp/README.md | 59 +++++++++++ fakesp/package.json | 22 ++++ fakesp/server.js | 193 +++++++++++++++++++++++++++++++++++ fakesp/views/error.jade | 8 ++ fakesp/views/index.jade | 45 ++++++++ fakesp/views/login-form.jade | 67 ++++++++++++ localtunnel/localtunnel.js | 124 ++++++++++++++++++++++ localtunnel/package.json | 13 +++ package.json | 1 - 12 files changed, 624 insertions(+), 2 deletions(-) create mode 100755 ez_dev.sh create mode 100644 fakesp/README.md create mode 100644 fakesp/package.json create mode 100644 fakesp/server.js create mode 100644 fakesp/views/error.jade create mode 100644 fakesp/views/index.jade create mode 100644 fakesp/views/login-form.jade create mode 100644 localtunnel/localtunnel.js create mode 100644 localtunnel/package.json diff --git a/.gitignore b/.gitignore index 0760e7c..cafdfd0 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist app/bower_components coverage .env +*.log diff --git a/Gruntfile.js b/Gruntfile.js index ef0d491..795fdfe 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -80,7 +80,7 @@ module.exports = function (grunt) { }, livereload: { options: { - open: 'http://<%=hostname %>:<%=port %>', + //open: 'http://<%=hostname %>:<%=port %>', base: [ '.tmp', '<%= yeoman.app %>' diff --git a/ez_dev.sh b/ez_dev.sh new file mode 100755 index 0000000..a8fe9da --- /dev/null +++ b/ez_dev.sh @@ -0,0 +1,91 @@ +#! /bin/bash + +echo "You'll need to know your Stormpath api key id, api key secret and application href in order to proceed" +echo + +echo "Setting up..." +echo + +echo "Executing: npm install..." +npm install + +if [ $? -eq 0 ] +then + echo "Successfully ran: npm install" +else + echo "npm install failed" +fi + +echo "Executing: bower install..." +bower install + +if [ $? -eq 0 ] +then + echo "Successfully ran: bower install" +else + echo "bower install failed" +fi + +echo "Setting up localtunnel..." +cd localtunnel +npm install + +if [ $? -eq 0 ] +then + echo "Successfully setup localtunnel" +else + echo "Failed to setup localtunnel" +fi + +cd .. + +echo "Setting up fakesp..." +cd fakesp +npm install + +if [ $? -eq 0 ] +then + echo "Successfully setup fakesp" +else + echo "Failed to setup fakesp" +fi + +cd .. +echo + +read -e -p "Enter your Stormpath API Key ID and press [ENTER]: " STORMPATH_CLIENT_APIKEY_ID +[ -z "${STORMPATH_CLIENT_APIKEY_ID}" ] && echo "Must specify a Stormpath API Key ID" && exit 1 + +read -e -p "Enter your Stormpath API Key Secret and press [ENTER]: " STORMPATH_CLIENT_APIKEY_SECRET +[ -z "${STORMPATH_CLIENT_APIKEY_SECRET}" ] && echo "Must specify a Stormpath API Key Secret" && exit 1 + +read -e -p "Enter your Stormpath Application HREF and press [ENTER]: " STORMPATH_APPLICATION_HREF +[ -z "${STORMPATH_APPLICATION_HREF}" ] && echo "Must specify a Stormpath Application HREF" && exit 1 + +read -e -p "Enter local domain and press [ENTER] (default: localhost): " DOMAIN +[ -z "${DOMAIN}" ] && DOMAIN=localhost + +export STORMPATH_CLIENT_APIKEY_ID STORMPATH_CLIENT_APIKEY_SECRET STORMPATH_APPLICATION_HREF DOMAIN + +echo +echo "Running fakesp..." +cd fakesp +EXPR="node server.js > ../fakesp.log 2>&1 &" +eval $EXPR +FAKESP_PID=$! +cd .. + +echo "Running localtunnel..." +cd localtunnel +EXPR="node localtunnel.js > ../localtunnel.log 2>&1 &" +eval $EXPR +LOCALTUNNEL_PID=$! +cd .. + +echo "Launching browser..." +sleep 1 +open http://localhost:8001 + +echo +echo "Running grunt serve..." +grunt serve diff --git a/fakesp/README.md b/fakesp/README.md new file mode 100644 index 0000000..dcec848 --- /dev/null +++ b/fakesp/README.md @@ -0,0 +1,59 @@ +### Fake service provider + +Use this to imitate a service-provider initiated login flow for the +Stormpath ID Site Feature. The repo contains a simple HTTP server +that serves a webapp which will redirect you to ID site for authentication. +When you return from ID site, a local cookie-based session will be created +for you. This is different from the SSO session which ID site retains +for you. + +### Installation + +`git clone` this repo, then `cd` into the folder + +`npm install` inside the folder + +### Configuration - Your Dev Machine + +Point `stormpath.localhost` to `127.0.0.1` by putting an entry into `/etc/hosts` + +Export the configuration for a Stormpath Application, with API keys, to your environment: + + export STORMPATH_CLIENT_APIKEY_ID=XXX + export STORMPATH_CLIENT_APIKEY_SECRET=XXX + export STORMPATH_APPLICATION_HREF=https://api.stormpath.com/v1/applications/XXX + +### Configuration - Your Stormpath Tenant + +Login to your Stormpath Tenant and add the following URLs to the list of +"Authorized Redirect URLs" in your ID Site configuration: + + http://stormpath.localhost:8001/idSiteCallback + +### Start the Server + +You can run the server with default options like so: + +``` +node server.js +``` + +Or with options: + +``` +ID_SITE_PATH= \ +PORT=8001 \ +DOMAIN=stormpath.localhost \ +CB_URI=http://stormpath.localhost:8001/idSiteCallback \ +node server.js +``` + +If the server starts sucessfully, you will see this message in your console: + +``` +Starting server on port 8001 +Server running, open this URL in your browser: +http://stormpath.localhost:8001 +``` + +You can now use the application by navigating to http://stormpath.localhost:8001 \ No newline at end of file diff --git a/fakesp/package.json b/fakesp/package.json new file mode 100644 index 0000000..f851751 --- /dev/null +++ b/fakesp/package.json @@ -0,0 +1,22 @@ +{ + "name": "fakesp", + "version": "0.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "client-sessions": "^0.7.0", + "express": "^4.9.6", + "jade": "^1.7.0", + "njwt": "^0.2.2", + "node-uuid": "^1.4.1", + "open": "0.0.5", + "request": "^2.70.0", + "stormpath": "^0.15.4" + } +} diff --git a/fakesp/server.js b/fakesp/server.js new file mode 100644 index 0000000..63a49c8 --- /dev/null +++ b/fakesp/server.js @@ -0,0 +1,193 @@ +var express = require('express'); +var nJwt = require('njwt'); +var sessions = require('client-sessions'); +var stormpath = require('stormpath'); +var request = require('request'); +var url = require('url'); + +var IS_PRODUCTION = process.env.NODE_ENV==='production'; +var PORT = process.env.PORT || 8001; +var DOMAIN = process.env.DOMAIN || 'stormpath.localhost'; +var ID_SITE_PATH = process.env.ID_SITE_PATH || ''; +var CB_URI = process.env.CB_URI || ('http://' + DOMAIN + ':' + PORT + '/idSiteCallback' ); + +var app = express(); +var application; +var client = new stormpath.Client(); + +app.set('views', './views'); +app.set('view engine', 'jade'); + + +var spCookieInterface = sessions({ + cookieName: 'sp', // cookie name dictates the key name added to the request object + secret: 'will be set after client initialization', // should be a large unguessable string + duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms + activeDuration: 1000 * 60 * 5 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds +}); + +var lastJwtCookieInterface = sessions({ + cookieName: 'lastJwt', // cookie name dictates the key name added to the request object + secret: 'will be set after client initialization', // should be a large unguessable string + duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms + activeDuration: 1000 * 60 * 5 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds +}); + +var idSiteBaseUrlCookieInterface = sessions({ + cookieName: 'idSiteBaseUrl', // cookie name dictates the key name added to the request object + secret: 'will be set after client initialization', // should be a large unguessable string + duration: 24 * 60 * 60 * 1000, // how long the session will stay valid in ms + activeDuration: 1000 * 60 * 5 // if expiresIn < activeDuration, the session will be extended by activeDuration milliseconds +}); + +app.use(lastJwtCookieInterface); +app.use(spCookieInterface); +app.use(idSiteBaseUrlCookieInterface); + +app.get('/', function(req, res){ + if(req.sp && req.sp.accountHref){ + client.getAccount(req.sp.accountHref,function(err,account){ + if(err){ + res.render('error',{ + errorText: String(err) + }); + }else{ + res.render('index',{ + lastJwt: req.lastJwt.value ? JSON.stringify(req.lastJwt.value,null,2) : null, + account: account, + accountJson: JSON.stringify(account,null,2), + cb_uri: CB_URI, + idSiteBaseUrl: req.idSiteBaseUrl.value + }); + } + }); + }else{ + + res.render('index',{ + lastJwt: req.lastJwt.value ? JSON.stringify(req.lastJwt.value,null,2) : null, + account: null, + cb_uri: CB_URI, + idSiteBaseUrl: req.idSiteBaseUrl.value + }); + + } +}); + +app.get('/idSiteCallback',function(req,res){ + if(req.query.jwtResponse){ + application.handleIdSiteCallback(req.url,function(err,idSiteResult){ + if(err){ + res.render('error',{ + errorText: JSON.stringify(err) + }); + }else{ + if(idSiteResult.status !== 'LOGOUT'){ + req.sp.accountHref = idSiteResult.account.href; + } + req.lastJwt.value = nJwt.verify(req.query.jwtResponse,client.config.client.apiKey.secret); + res.redirect('/'); + } + }); + } +}); + + +app.get('/login', function(req, res){ + + var options = { + callbackUri: req.query.cb_uri || CB_URI, + path: ID_SITE_PATH + }; + + if(req.query.sof){ + options.showOrganizationField = Boolean(parseInt(req.query.sof,10)); + } + + if(req.query.onk){ + options.organizationNameKey = req.query.onk; + } + + if(req.query.usd){ + options.useSubDomain = true; + } + + if(req.query.state){ + options.state = req.query.state; + } + + if(req.query.path){ + options.path = req.query.path; + } + var ssoRequestUrl = application.createIdSiteUrl(options); + + if(req.query.idSiteBaseUrl) { + var parsedIdSiteBaseUrl = url.parse(req.query.idSiteBaseUrl); + var idSiteBaseUrl = parsedIdSiteBaseUrl.protocol + '//' + parsedIdSiteBaseUrl.host; + req.idSiteBaseUrl.value = idSiteBaseUrl; + + request({ + method: 'GET', + url: ssoRequestUrl, + followRedirect: false + }, function (err, response, body) { + var idSiteRedirectUrl = response.headers.location || ''; + if (err) { + res.json(err); + } else if(response.statusCode !== 302) { + res.json(body); + } else if(idSiteRedirectUrl.match(/jwtResponse/)) { + res.redirect(response.location); + } else if(req.query.idSiteBaseUrl) { + var parts = idSiteRedirectUrl.split('/#'); + var newUrl = idSiteBaseUrl + '/#' + parts[1]; + res.setHeader('Location', newUrl); + res.status(302); + res.end(); + } else { + res.redirect(idSiteRedirectUrl); + } + }); + } else { + req.idSiteBaseUrl.value = ''; + res.redirect(ssoRequestUrl); + } + +}); + +app.get('/logout', function(req, res){ + req.sp.destroy(); + res.redirect(application.createIdSiteUrl({ + callbackUri: CB_URI, + logout: true + })); +}); + + + + +function startServer(){ + lastJwtCookieInterface.secret = client.config.apiKey.secret; + spCookieInterface.secret = client.config.apiKey.secret; + console.log('Starting server on port ' + PORT); + app.listen(PORT,function(){ + console.log('Server running, open this URL in your browser:'); + console.log('http://'+DOMAIN+':'+PORT); + }); + +} + +function getApplication(then){ + client.getApplication(client.config.application.href,function(err,a){ + if (err){ + throw err; + } + application = a; + then(); + }); +} + + +client.on('ready',function(){ + getApplication(startServer); +}); + diff --git a/fakesp/views/error.jade b/fakesp/views/error.jade new file mode 100644 index 0000000..82b84d3 --- /dev/null +++ b/fakesp/views/error.jade @@ -0,0 +1,8 @@ +html + head + link(rel='stylesheet', type='text/css', href='//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css') + body + pre.alert.alert-danger=errorText + p   + center + a.btn.btn-primary.btn-lg(href="/") Return to home page \ No newline at end of file diff --git a/fakesp/views/index.jade b/fakesp/views/index.jade new file mode 100644 index 0000000..0ca3a78 --- /dev/null +++ b/fakesp/views/index.jade @@ -0,0 +1,45 @@ +html + head + link(rel="stylesheet", type="text/css", href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css") + body + div.container + if !account + p   + p   + include login-form + + + if account + h2 You are loggen in + hr + p + | You have a cookie-based session, tied to this server. You can now do one of the following: + table.table.table-bordered + tr + td + p + | If your SSO cookie is still valid you will be immediately redirected back here. + | If your SSO cookie has expired you will be presented with the ID Site login application + include login-form + + tr + td + p + | This will send you to the ID Site with a logout request. + | You will return to this application and your session will be destroyed + div.panel.panel-default + div.panel-heading Logout Request + div.panel-body + center + a.btn.btn-primary(href='/logout') Logout + .panel.panel-default + .panel-heading + h3.panel-title Account of current session: + .panel-body + pre=accountJson + if lastJwt + div.panel.panel-default + div.panel-heading + h3.panel-title Last seen JWT: + div.panel-body + pre=lastJwt \ No newline at end of file diff --git a/fakesp/views/login-form.jade b/fakesp/views/login-form.jade new file mode 100644 index 0000000..c4f3def --- /dev/null +++ b/fakesp/views/login-form.jade @@ -0,0 +1,67 @@ +script(type="application/javascript"). + function revertIdSiteBaseUrl() { + var idSiteBaseUrlInupt = document.getElementById('idSiteBaseUrl'); + idSiteBaseUrlInupt.value = ''; + } +form.form-horizontal(action="/login",method="get") + div.panel.panel-default + div.panel-heading + h3.panel-title Login Request + div.panel-body + div.form-group + label.control-label.col-xs-4 Show Organization Field + div.col-xs-6.col-sm-3.col-md-2 + div.radio + label + input(type="radio" name="sof" checked value="") + span Undefined + div.radio + label + input(type="radio" name="sof" value="1") + span True + div.radio + label + input(type="radio" name="sof" value="0") + span False + + div.form-group + label.control-label.col-xs-4 Organization Name Key + div.col-xs-6.col-sm-3.col-md-2 + input.form-control(type="text" name="onk") + + div.form-group + label.control-label.col-xs-4 Use Subdomain + div.col-xs-6.col-sm-3.col-md-2 + div.checkbox + label + input(type="checkbox" name="usd") + + div.form-group + label.control-label.col-xs-4 State + div.col-xs-6.col-sm-3.col-md-2 + input.form-control(type="text" name="state") + + div.form-group + label.control-label.col-xs-4 Path + div.col-xs-6.col-sm-3.col-md-2 + input.form-control(type="text" name="path") + + div.form-group + label.control-label.col-xs-4 Callback URI + div.col-xs-6.col-sm-3.col-md-5 + input.form-control(type="text" name="cb_uri" value=cb_uri) + + div.form-group + label.control-label.col-xs-4 Alternate ID Site Base URL + div.col-xs-6.col-sm-3.col-md-5 + div.input-group + input.form-control(type="text" name="idSiteBaseUrl" id="idSiteBaseUrl" value=idSiteBaseUrl) + + div.input-group-addon(style="cursor:pointer;" onclick="revertIdSiteBaseUrl(this)") + i.icon.glyphicon.glyphicon-trash + span.help-block Use for local ID Site development. Note: will not persist SSO cookie. + + div.form-group + label.control-label.col-xs-4 + div.col-xs-6.col-sm-3.col-md-2 + button.btn.btn-primary.form-control(type="submit") Log me in! \ No newline at end of file diff --git a/localtunnel/localtunnel.js b/localtunnel/localtunnel.js new file mode 100644 index 0000000..d0932e1 --- /dev/null +++ b/localtunnel/localtunnel.js @@ -0,0 +1,124 @@ +var localtunnel = require('localtunnel'); +var stormpath = require('stormpath'); + +var callbckUri = 'http://stormpath.localhost:8001/idSiteCallback'; +var client = new stormpath.Client(); +var doCleanup = false; +var previousDomainName = null; +var host = null; + +/*eslint no-console: 0*/ + +function prepeareIdSiteModel(client, idSiteApplicationHost, callbckUri, cb) { + client.getCurrentTenant(function(err, tenant) { + if (err) { + throw err; + } + + client.getResource(tenant.href + '/idSites', function(err, collection) { + if (err) { + throw err; + } + + var idSiteModel = collection.items[0]; + + previousDomainName = idSiteModel.domainName; + + idSiteModel.domainName = idSiteApplicationHost.split('//')[1]; + + if (idSiteModel.authorizedOriginUris.indexOf(idSiteApplicationHost) === -1) { + idSiteModel.authorizedOriginUris.push(idSiteApplicationHost); + idSiteModel.authorizedOriginUris.push(idSiteApplicationHost.replace('https','http')); + } + + if (idSiteModel.authorizedRedirectUris.indexOf(callbckUri) === -1) { + idSiteModel.authorizedRedirectUris.push(callbckUri); + } + + idSiteModel.save(cb); + }); + }); +} + +function revertIdSiteModel(client, idSiteApplicationHost, callbckUri, cb) { + client.getCurrentTenant(function(err, tenant) { + if (err) { + throw err; + } + + client.getResource(tenant.href + '/idSites', function(err, collection) { + if (err) { + throw err; + } + + var idSiteModel = collection.items[0]; + + idSiteModel.domainName = previousDomainName; + + idSiteModel.authorizedOriginUris = idSiteModel.authorizedOriginUris.filter(function(uri) { + return !uri.match(idSiteApplicationHost); + }); + + idSiteModel.authorizedOriginUris = idSiteModel.authorizedOriginUris.filter(function(uri) { + return !uri.match(idSiteApplicationHost.replace('https','http')); + }); + + idSiteModel.authorizedRedirectUris = idSiteModel.authorizedRedirectUris.filter(function(uri) { + return !uri.match(callbckUri); + }); + + idSiteModel.save(cb); + }); + }); +} + +function cleanup(cb){ + if(doCleanup){ + revertIdSiteModel(client,host,callbckUri,function (err) { + if (err) { + throw err; + } + console.log('ID Site Model Restored'); + cb(); + }); + } +} + +var tunnel = localtunnel(process.env.PORT || 9000, function(err, tunnel) { + if (err) { + console.error(err); + return process.exit(1); + } + host = tunnel.url; + console.log(host); + prepeareIdSiteModel(client,host,callbckUri,function(err){ + if (err) { + throw err; + } + console.log('ID Site Model Ready'); + setInterval(function () { },1000); + doCleanup = true; + }); + +}); + +tunnel.on('error', function (err) { + console.error(err); + cleanup(); +}); + +tunnel.on('close', cleanup); + +process.on('SIGTERM', function() { + console.log('\nCaught termination signal'); + cleanup(function (){ + process.exit(); + }); +}); + +process.on('SIGINT', function() { + console.log('\nCaught interrupt signal'); + cleanup(function (){ + process.exit(); + }); +}); diff --git a/localtunnel/package.json b/localtunnel/package.json new file mode 100644 index 0000000..db0c794 --- /dev/null +++ b/localtunnel/package.json @@ -0,0 +1,13 @@ +{ + "name": "stormpathidp", + "version": "0.5.0", + "devDependencies": { + "localtunnel": "^1.7.0", + "stormpath": "^0.18.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "license": "Apache-2.0", + "homepage": "https://github.com/stormpath/idsite-src" +} diff --git a/package.json b/package.json index 381517e..97f4333 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "protractor": "^0.22.0", "q": "^1.4.1", "request": "^2.58.0", - "stormpath": "^0.18.2", "time-grunt": "~0.2.1", "untildify": "^2.0.0" }, From 5238647a15f874174b961a9eb0e096e7648768e7 Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Thu, 30 Jun 2016 23:24:31 -0400 Subject: [PATCH 08/16] WIP --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 62530cf..7ceb3f2 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ npm install bower install ``` +![tunnel](https://github.com/stormpath/idsite-src/blob/media/docs_images/idsite_tunnel_dev.png) + ## Setup an HTTPS proxy Because ID Site only works with HTTPS, you will need to setup a local tunnel From 947b67a89a891e3d17bb5eb92d3680e485f3323f Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Fri, 1 Jul 2016 00:40:36 -0400 Subject: [PATCH 09/16] ensure that DOMAIN setting is used in localtunnel.js. ez_dev.sh will use STORMPATH_API_KEY_FILE if set or will use default apiKey.properties, if found. --- README.md | 67 +++++++++++++++----------------------- ez_dev.sh | 48 +++++++++++++++++---------- fakesp/server.js | 3 +- localtunnel/localtunnel.js | 5 ++- 4 files changed, 61 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 7ceb3f2..0e8321e 100644 --- a/README.md +++ b/README.md @@ -36,64 +36,48 @@ It is assumed that you have the following tools installed on your computer * [Node.JS][] * [Localtunnel.me][] -You should clone this repository and these tasks within the repository: +You should clone this repository and then run this within the repository: ```sh npm install bower install ``` -![tunnel](https://github.com/stormpath/idsite-src/blob/media/docs_images/idsite_tunnel_dev.png) +## Using `ez_dev` -## Setup an HTTPS proxy +Within this repository, there's a script called `ez_dev.sh`. This will setup a complete development environment for you +to be able to work on your ID Site Content. -Because ID Site only works with HTTPS, you will need to setup a local tunnel -which will serve your your local ID Site from a secure connection. With the -local tunnel tool you must do this: +Before we get into running the script, let's take a step back to see what `ez_dev` sets up. -> lt --port 9000 +A typical flow for using ID Site is the following: -It will fetch a URL and tell you something like this: +1. You make a request of the `/login` endpoint of your application in the browser. +2. Your application redirects to Stormapth (api.stormpath.com) +3. Stormpath redirects to the ID Site configured in the Stormpath Admin Console +4. ID Site responds to your browser with the `login` view -> your url is: https://wqdkeiseuj.localtunnel.me +When developing locally, you need an application that will connect to ID Site. And, in particular, ID Site being served +locally from the `idsite-src` repo that you cloned so that you can rapidly make changes and see them in action in real +time. And, there's a requirement that the traffic SSL encrypted. -You must take that URL and configure your ID Site accordingly. Please login -to the [Stormpath Admin Console][] and set these options on your ID Site -Configuration: +The `ez_dev` script sets up the necessary architecture needed. The components are: -| Configuration Option | Should be set to | -|----------------------------------------|-------------------------------------------------------------------------------------| -| **Domain Name** | your local tunnel URL | -| **Authorized Javascript Origin URLs** | your local tunnel URL should be in this list | -| **Authorized Redirect URLs** | the endpoint on your server application which will receive the user after ID site (read below) | +1. `fakesp` - A minimal application that will connect to ID Site +2. `localtunnel.me` - A free service that sets up an HTTPS proxy from the public internet to a locally running server. +3. `grunt serve` - A locally running instance of ID Site. -## Your Service Provider (required) +Once this is all running, you browser will automatically open up to: `http://localhost:8001` and you will be able to see the +updated ID Site content you are working on. Any saved changes to the ID Site content are immediately reflected in your browser +when you go to an ID Site view (such as `/login`). -The application (typically, your server) that sends the user to ID Site is known -as the Service Provider (SP). You send the user to ID Site by constructing a -redirect URL with one of our SDKs. For example, [createIdSiteUrl()][] in our -Node.js SDK. +Here's a visual representation of what is setup and the flow of the requests. -After the user authenticates at ID Site, the user is redirected back to your -application. Your application must have a callback URL which receives the user -and validates the `jwtResponse` parameter in the URL (our SDK does this work -for you). - -If you haven't built your service provider we have a simple service provider -application which you can use for testing purposes, see: [Fake SP][] - - -## Startup - -Once you have setup the environment (the steps above) you are ready to start -the development tasks. Run the following command to start the server: - -> grunt serve - -This will open the application your browser. Because the application does not -have a JWT request, you will see the JWT error. At this point you should use -your service provider to redirect the user to your ID Site. +![tunnel architecture][tunnel_image] +Note: The `ez_dev` script alters the ID Site settings in your Stormpath Admin Console. When you are done working on +ID Site, it is recommended that you go back to your Admin Console and revert the `Domain Name`, +`Authorized Javascript Origin URLs`, and `Authorized Redirect URLs` settings. ## Development Process - Stormpath Tenants @@ -185,3 +169,4 @@ License](http://www.apache.org/licenses/LICENSE-2.0). [Node.JS]: http://nodejs.org [Stormpath Admin Console]: https://api.stormpath.com [Stormpath.js]: https://github.com/stormpath/stormpath.js +[tunnel_image]: https://github.com/stormpath/idsite-src/blob/media/docs_images/idsite_tunnel_dev.png diff --git a/ez_dev.sh b/ez_dev.sh index a8fe9da..4592ecc 100755 --- a/ez_dev.sh +++ b/ez_dev.sh @@ -1,11 +1,38 @@ #! /bin/bash -echo "You'll need to know your Stormpath api key id, api key secret and application href in order to proceed" -echo +function get_property +{ + FILE=$1 + PROPERTY=$2 + sed '/^\#/d' $FILE | grep $PROPERTY | tail -n 1 | cut -d "=" -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' +} echo "Setting up..." echo +if [ -n "${STORMPATH_API_KEY_FILE}" ]; then + STORMPATH_CLIENT_APIKEY_ID=`get_property ${STORMPATH_API_KEY_FILE} "apiKey.id"` + STORMPATH_CLIENT_APIKEY_SECRET=`get_property ${STORMPATH_API_KEY_FILE} "apiKey.secret"` +elif [ -a ~/.stormpath/apiKey.properties ]; then + STORMPATH_CLIENT_APIKEY_ID=`get_property ~/.stormpath/apiKey.properties "apiKey.id"` + STORMPATH_CLIENT_APIKEY_SECRET=`get_property ~/.stormpath/apiKey.properties "apiKey.secret"` +fi + +[ -z "${STORMPATH_CLIENT_APIKEY_ID}" ] && read -e -p "Enter your Stormpath API Key ID and press [ENTER]: " STORMPATH_CLIENT_APIKEY_ID +[ -z "${STORMPATH_CLIENT_APIKEY_ID}" ] && echo "Must specify a Stormpath API Key ID" && exit 1 + +[ -z "${STORMPATH_CLIENT_APIKEY_SECRET}" ] && read -e -p "Enter your Stormpath API Key Secret and press [ENTER]: " STORMPATH_CLIENT_APIKEY_SECRET +[ -z "${STORMPATH_CLIENT_APIKEY_SECRET}" ] && echo "Must specify a Stormpath API Key Secret" && exit 1 + +[ -z "${STORMPATH_APPLICATION_HREF}" ] && read -e -p "Enter your Stormpath Application HREF and press [ENTER] (default: default Stormpath Application): " STORMPATH_APPLICATION_HREF + +read -e -p "Enter local domain and press [ENTER] (default: localhost): " DOMAIN +[ -z "${DOMAIN}" ] && DOMAIN=localhost + +export STORMPATH_CLIENT_APIKEY_ID STORMPATH_CLIENT_APIKEY_SECRET STORMPATH_APPLICATION_HREF DOMAIN + +echo + echo "Executing: npm install..." npm install @@ -53,21 +80,6 @@ fi cd .. echo -read -e -p "Enter your Stormpath API Key ID and press [ENTER]: " STORMPATH_CLIENT_APIKEY_ID -[ -z "${STORMPATH_CLIENT_APIKEY_ID}" ] && echo "Must specify a Stormpath API Key ID" && exit 1 - -read -e -p "Enter your Stormpath API Key Secret and press [ENTER]: " STORMPATH_CLIENT_APIKEY_SECRET -[ -z "${STORMPATH_CLIENT_APIKEY_SECRET}" ] && echo "Must specify a Stormpath API Key Secret" && exit 1 - -read -e -p "Enter your Stormpath Application HREF and press [ENTER]: " STORMPATH_APPLICATION_HREF -[ -z "${STORMPATH_APPLICATION_HREF}" ] && echo "Must specify a Stormpath Application HREF" && exit 1 - -read -e -p "Enter local domain and press [ENTER] (default: localhost): " DOMAIN -[ -z "${DOMAIN}" ] && DOMAIN=localhost - -export STORMPATH_CLIENT_APIKEY_ID STORMPATH_CLIENT_APIKEY_SECRET STORMPATH_APPLICATION_HREF DOMAIN - -echo echo "Running fakesp..." cd fakesp EXPR="node server.js > ../fakesp.log 2>&1 &" @@ -88,4 +100,4 @@ open http://localhost:8001 echo echo "Running grunt serve..." -grunt serve +grunt serve diff --git a/fakesp/server.js b/fakesp/server.js index 63a49c8..2dd77c7 100644 --- a/fakesp/server.js +++ b/fakesp/server.js @@ -7,7 +7,7 @@ var url = require('url'); var IS_PRODUCTION = process.env.NODE_ENV==='production'; var PORT = process.env.PORT || 8001; -var DOMAIN = process.env.DOMAIN || 'stormpath.localhost'; +var DOMAIN = process.env.DOMAIN || 'localhost'; var ID_SITE_PATH = process.env.ID_SITE_PATH || ''; var CB_URI = process.env.CB_URI || ('http://' + DOMAIN + ':' + PORT + '/idSiteCallback' ); @@ -190,4 +190,3 @@ function getApplication(then){ client.on('ready',function(){ getApplication(startServer); }); - diff --git a/localtunnel/localtunnel.js b/localtunnel/localtunnel.js index d0932e1..7ad77b7 100644 --- a/localtunnel/localtunnel.js +++ b/localtunnel/localtunnel.js @@ -1,7 +1,10 @@ var localtunnel = require('localtunnel'); var stormpath = require('stormpath'); -var callbckUri = 'http://stormpath.localhost:8001/idSiteCallback'; +var DOMAIN = process.env.DOMAIN || 'localhost'; +var PORT = process.env.PORT || 8001; + +var callbckUri = process.env.CB_URI || ('http://' + DOMAIN + ':' + PORT + '/idSiteCallback' ); var client = new stormpath.Client(); var doCleanup = false; var previousDomainName = null; From 6d2f5faee77f86932e64da7582a5be78faeb7232 Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Fri, 1 Jul 2016 00:53:56 -0400 Subject: [PATCH 10/16] ez_dev.sh and README.md updates --- README.md | 38 ++++++++++++++++++++++++++++++++++++-- ez_dev.sh | 1 - 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0e8321e..04c1c80 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,13 @@ npm install bower install ``` -## Using `ez_dev` +## `ez_dev` Within this repository, there's a script called `ez_dev.sh`. This will setup a complete development environment for you to be able to work on your ID Site Content. -Before we get into running the script, let's take a step back to see what `ez_dev` sets up. +Before we get into running the script, let's take a step back to see what `ez_dev` sets up. If you want to jump right +in, go to the [running ez_dev](#running-ez_dev) section. A typical flow for using ID Site is the following: @@ -75,6 +76,39 @@ Here's a visual representation of what is setup and the flow of the requests. ![tunnel architecture][tunnel_image] +### Running `ez_dev` + +`ez_dev` needs to know the following: + +1. Your Stormpath API Key ID +2. Your Stormpath API Key secret +3. Your Stormpath Application HREF + +The script will look for some common environment variables and file locations in this order: + +1. If `STORMPATH_API_KEY_FILE` is set, it will get the API Key ID and API Key Secret from that file +2. If `~/.stormpath/apiKey.properties` exists, it will get the API Key ID and API Key Secret from that file +3. If `STORMPATH_APPLICATION_HREF` is set, it will use that value for the Stormpath Application to connect to + +If neither 1. or 2. above is met, you will be asked to provide the API Key ID and the API Key Secret to the script + +If 3. above is not met, you will be asked to provide the Application HREF. *Note*: You can leave this value blank if +you only have the default application (`My Application`) defined in your Stormpath tenant from when it was first setup. +If you have any other Applications defined, then you must specify the Application HREF. + +Here are a few run scenarios: + +``` +./ez_dev.sh # may be asked to provide additional information +``` + +``` +# explicit settings - you will not be asked for any additional information +STORMPATH_API_KEY_FILE=~/.stormpath/apiKey.mytenant.properties \ +STORMPATH_APPLICATION_HREF=https://api.stormpath.com/v1/applications/stormpathidentifier123456 \ +./ez_dev.sh +``` + Note: The `ez_dev` script alters the ID Site settings in your Stormpath Admin Console. When you are done working on ID Site, it is recommended that you go back to your Admin Console and revert the `Domain Name`, `Authorized Javascript Origin URLs`, and `Authorized Redirect URLs` settings. diff --git a/ez_dev.sh b/ez_dev.sh index 4592ecc..5a6415f 100755 --- a/ez_dev.sh +++ b/ez_dev.sh @@ -26,7 +26,6 @@ fi [ -z "${STORMPATH_APPLICATION_HREF}" ] && read -e -p "Enter your Stormpath Application HREF and press [ENTER] (default: default Stormpath Application): " STORMPATH_APPLICATION_HREF -read -e -p "Enter local domain and press [ENTER] (default: localhost): " DOMAIN [ -z "${DOMAIN}" ] && DOMAIN=localhost export STORMPATH_CLIENT_APIKEY_ID STORMPATH_CLIENT_APIKEY_SECRET STORMPATH_APPLICATION_HREF DOMAIN From 6de21ede5da3076660450c6204e920e76d4a0a28 Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Tue, 12 Jul 2016 15:20:12 -0400 Subject: [PATCH 11/16] removed commented out open call. ez_dev.sh handles this now. --- Gruntfile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 795fdfe..0bc4ac0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -80,7 +80,6 @@ module.exports = function (grunt) { }, livereload: { options: { - //open: 'http://<%=hostname %>:<%=port %>', base: [ '.tmp', '<%= yeoman.app %>' From af43a7ffe9b0e2af50e08bb68e59652594a7e53d Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 13 Jul 2016 10:45:08 -0700 Subject: [PATCH 12/16] wordsmithing the readme --- README.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 04c1c80..16a63d7 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,19 @@ intuitive API and expert support make it easy for developers to authenticate, manage, and secure users and roles in any application. This is the development environment for the Stormpath hosted ID Site. You can -use this repository to build the same single page application (SPA) that -Stormpath provides by default. The SPA uses Angular and Browserify and it is -built using Grunt and Yeoman. +fork this repository and use it to build the same single page application (SPA) +that Stormpath provides by default, and you can make any changes that you +require for your custom version. -If you want to build your own ID Site and are comfortable with Angular, you should -use this repository. - -If you are not using Angular, you can build your own ID Site from scratch -but you will need to use [Stormpath.js][] in order to communicate with our API -from your custom ID Site application. +This default application is built with Angular and has default views and styling. +If you need to make significant changes that don't fit within this application, +you may want to use [Stormpath.js][] instead. That is a smaller library that +gives you the necessary Stormpath APIs to do user management, but does not come +with pre-built views or styling. ## Browser Support -ID Site will work in the following web browser environments: +This ID Site application will work in the following web browser environments: * Chrome (all versions) * Internet Explorer 10+ @@ -43,28 +42,31 @@ npm install bower install ``` -## `ez_dev` +## Getting started with `ez_dev` Within this repository, there's a script called `ez_dev.sh`. This will setup a complete development environment for you -to be able to work on your ID Site Content. +to be able to work on your ID Site application. Before we get into running the script, let's take a step back to see what `ez_dev` sets up. If you want to jump right in, go to the [running ez_dev](#running-ez_dev) section. A typical flow for using ID Site is the following: -1. You make a request of the `/login` endpoint of your application in the browser. -2. Your application redirects to Stormapth (api.stormpath.com) -3. Stormpath redirects to the ID Site configured in the Stormpath Admin Console -4. ID Site responds to your browser with the `login` view +1. Your make a request of the `/login` endpoint of your application in the browser. +2. Your application redirects the browser to Stormapth (api.stormpath.com/sso). +3. Stormpath redirects the browser to your ID Site, as configured in the Stormpath Admin Console. +4. The ID Site application loads in your browser and presents the login form. +5. After login or registration, your browser is directed back to the server from step 1. + +When customizing ID Site on your local machine, you need: -When developing locally, you need an application that will connect to ID Site. And, in particular, ID Site being served -locally from the `idsite-src` repo that you cloned so that you can rapidly make changes and see them in action in real -time. And, there's a requirement that the traffic SSL encrypted. +* A web server to handle steps 1 and 5 +* A web server to serve the assets at step 4, that would usually be handled by Stormpath. +* SSL encryption for step 3, as required by Stormpath. The `ez_dev` script sets up the necessary architecture needed. The components are: -1. `fakesp` - A minimal application that will connect to ID Site +1. `fakesp` - A web server that will handle steps 1 and 5. 2. `localtunnel.me` - A free service that sets up an HTTPS proxy from the public internet to a locally running server. 3. `grunt serve` - A locally running instance of ID Site. From 36306188d83609077a42a3ebcef799f9d1a7202d Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 9 Sep 2016 09:42:29 -0700 Subject: [PATCH 13/16] switch to ngrok --- localtunnel/localtunnel.js | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/localtunnel/localtunnel.js b/localtunnel/localtunnel.js index 7ad77b7..d15cefd 100644 --- a/localtunnel/localtunnel.js +++ b/localtunnel/localtunnel.js @@ -1,4 +1,4 @@ -var localtunnel = require('localtunnel'); +var ngrok = require('ngrok'); var stormpath = require('stormpath'); var DOMAIN = process.env.DOMAIN || 'localhost'; @@ -87,12 +87,12 @@ function cleanup(cb){ } } -var tunnel = localtunnel(process.env.PORT || 9000, function(err, tunnel) { +ngrok.connect(process.env.PORT || 9000, function(err, url) { if (err) { console.error(err); return process.exit(1); } - host = tunnel.url; + host = url; console.log(host); prepeareIdSiteModel(client,host,callbckUri,function(err){ if (err) { @@ -105,12 +105,12 @@ var tunnel = localtunnel(process.env.PORT || 9000, function(err, tunnel) { }); -tunnel.on('error', function (err) { +ngrok.on('error', function (err) { console.error(err); cleanup(); }); -tunnel.on('close', cleanup); +ngrok.on('disconnect', cleanup); process.on('SIGTERM', function() { console.log('\nCaught termination signal'); diff --git a/package.json b/package.json index 97f4333..17ed218 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "karma-ng-html2js-preprocessor": "^0.1.0", "karma-ng-scenario": "^0.1.0", "load-grunt-tasks": "~0.4.0", - "localtunnel": "^1.7.0", "mocha": "^2.2.5", + "ngrok": "^2.2.2", "protractor": "^0.22.0", "q": "^1.4.1", "request": "^2.58.0", From 6c32c78b08585482387637708b7cf5c147c111dc Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Fri, 9 Sep 2016 13:54:44 -0400 Subject: [PATCH 14/16] Updated for ngrok. --- README.md | 4 ++-- ez_dev.sh | 22 +++++++++++--------- fakesp/README.md | 2 +- localtunnel/localtunnel.js => ngrok/ngrok.js | 0 {localtunnel => ngrok}/package.json | 2 +- 5 files changed, 16 insertions(+), 14 deletions(-) rename localtunnel/localtunnel.js => ngrok/ngrok.js (100%) rename {localtunnel => ngrok}/package.json (88%) diff --git a/README.md b/README.md index 16a63d7..49b7b52 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ It is assumed that you have the following tools installed on your computer * [Bower][] * [Grunt][] * [Node.JS][] -* [Localtunnel.me][] + +**Note**: At this time, only node version `4.x` is supported. You should clone this repository and then run this within the repository: @@ -201,7 +202,6 @@ License](http://www.apache.org/licenses/LICENSE-2.0). [Fake SP]: https://github.com/robertjd/fakesp [Grunt]: http://gruntjs.com [ID Site Repository]: https://github.com/stormpath/idsite -[Localtunnel.me]: http://localtunnel.me/ [Node.JS]: http://nodejs.org [Stormpath Admin Console]: https://api.stormpath.com [Stormpath.js]: https://github.com/stormpath/stormpath.js diff --git a/ez_dev.sh b/ez_dev.sh index 5a6415f..ffd4c45 100755 --- a/ez_dev.sh +++ b/ez_dev.sh @@ -10,6 +10,10 @@ function get_property echo "Setting up..." echo +hash npm 2>/dev/null || { echo >&2 "npm is required, but it's not installed. Install via: https://github.com/tj/n"; exit 1; } +hash bower 2>/dev/null || { echo >&2 "bower is required, but it's not installed. Install via: npm install -g bower"; exit 1; } +hash grunt 2>/dev/null || { echo >&2 "grunt is required, but it's not installed. Install via: npm install -g grunt-cli"; exit 1; } + if [ -n "${STORMPATH_API_KEY_FILE}" ]; then STORMPATH_CLIENT_APIKEY_ID=`get_property ${STORMPATH_API_KEY_FILE} "apiKey.id"` STORMPATH_CLIENT_APIKEY_SECRET=`get_property ${STORMPATH_API_KEY_FILE} "apiKey.secret"` @@ -30,8 +34,6 @@ fi export STORMPATH_CLIENT_APIKEY_ID STORMPATH_CLIENT_APIKEY_SECRET STORMPATH_APPLICATION_HREF DOMAIN -echo - echo "Executing: npm install..." npm install @@ -52,15 +54,15 @@ else echo "bower install failed" fi -echo "Setting up localtunnel..." -cd localtunnel +echo "Setting up ngrok..." +cd ngrok npm install if [ $? -eq 0 ] then - echo "Successfully setup localtunnel" + echo "Successfully setup ngrok" else - echo "Failed to setup localtunnel" + echo "Failed to setup ngrok" fi cd .. @@ -86,11 +88,11 @@ eval $EXPR FAKESP_PID=$! cd .. -echo "Running localtunnel..." -cd localtunnel -EXPR="node localtunnel.js > ../localtunnel.log 2>&1 &" +echo "Running ngrok..." +cd ngrok +EXPR="node ngrok.js > ../ngrok.log 2>&1 &" eval $EXPR -LOCALTUNNEL_PID=$! +NGROK_PID=$! cd .. echo "Launching browser..." diff --git a/fakesp/README.md b/fakesp/README.md index dcec848..82a3448 100644 --- a/fakesp/README.md +++ b/fakesp/README.md @@ -56,4 +56,4 @@ Server running, open this URL in your browser: http://stormpath.localhost:8001 ``` -You can now use the application by navigating to http://stormpath.localhost:8001 \ No newline at end of file +You can now use the application by navigating to http://stormpath.localhost:8001 diff --git a/localtunnel/localtunnel.js b/ngrok/ngrok.js similarity index 100% rename from localtunnel/localtunnel.js rename to ngrok/ngrok.js diff --git a/localtunnel/package.json b/ngrok/package.json similarity index 88% rename from localtunnel/package.json rename to ngrok/package.json index db0c794..ef0d30a 100644 --- a/localtunnel/package.json +++ b/ngrok/package.json @@ -2,7 +2,7 @@ "name": "stormpathidp", "version": "0.5.0", "devDependencies": { - "localtunnel": "^1.7.0", + "ngrok": "^2.2.2", "stormpath": "^0.18.2" }, "engines": { From c0a7ea8292db0f6fbb072c57666d52deb5ba31d9 Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Fri, 9 Sep 2016 17:07:29 -0400 Subject: [PATCH 15/16] Updated output and added kill script --- .gitignore | 1 + ez_dev.sh | 26 ++++++++-- kill_ez_dev.sh | 3 ++ localtunnel.js | 126 ------------------------------------------------- 4 files changed, 26 insertions(+), 130 deletions(-) create mode 100755 kill_ez_dev.sh delete mode 100644 localtunnel.js diff --git a/.gitignore b/.gitignore index cafdfd0..1e6dccd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ app/bower_components coverage .env *.log +kill_ez_dev.pids diff --git a/ez_dev.sh b/ez_dev.sh index ffd4c45..136f87e 100755 --- a/ez_dev.sh +++ b/ez_dev.sh @@ -95,10 +95,28 @@ eval $EXPR NGROK_PID=$! cd .. +echo "Running grunt serve..." +EXPR="grunt serve > grunt_serve.log 2>&1 &" +eval $EXPR +GRUNT_SERVE_PID=$! + +echo +echo "fakesp PID: $FAKESP_PID, ngrok PID: $NGROK_PID, grunt PID: $GRUNT_SERVE_PID" +echo + +echo $FAKESP_PID > kill_ez_dev.pids +echo $NGROK_PID >> kill_ez_dev.pids +echo $GRUNT_SERVE_PID >> kill_ez_dev.pids + +echo "Tail the logs for more information:" +echo -e "\tfakesp.log" +echo -e "\tngrok.log" +echo -e "\tgrunt_serve.log" +echo + +echo "run: ./kill_ez_dev.sh to kill ez dev" +echo + echo "Launching browser..." sleep 1 open http://localhost:8001 - -echo -echo "Running grunt serve..." -grunt serve diff --git a/kill_ez_dev.sh b/kill_ez_dev.sh new file mode 100755 index 0000000..b70017f --- /dev/null +++ b/kill_ez_dev.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +for i in `cat kill_ez_dev.pids`; do kill $i; done diff --git a/localtunnel.js b/localtunnel.js deleted file mode 100644 index 8700b6f..0000000 --- a/localtunnel.js +++ /dev/null @@ -1,126 +0,0 @@ -var localtunnel = require('localtunnel'); -var stormpath = require('stormpath'); - -var callbckUri = 'http://stormpath.localhost:8001/idSiteCallback'; -var client = new stormpath.Client(); -var doCleanup = false; -var previousDomainName = null; -var host = null; - -/*eslint no-console: 0*/ - -function prepeareIdSiteModel(client, idSiteApplicationHost, callbckUri, cb) { - client.getCurrentTenant(function(err, tenant) { - if (err) { - throw err; - } - - client.getResource(tenant.href + '/idSites', function(err, collection) { - if (err) { - throw err; - } - - var idSiteModel = collection.items[0]; - - previousDomainName = idSiteModel.domainName; - - idSiteModel.domainName = idSiteApplicationHost.split('//')[1]; - - if (idSiteModel.authorizedOriginUris.indexOf(idSiteApplicationHost) === -1) { - idSiteModel.authorizedOriginUris.push(idSiteApplicationHost); - idSiteModel.authorizedOriginUris.push(idSiteApplicationHost.replace('https','http')); - } - - if (idSiteModel.authorizedRedirectUris.indexOf(callbckUri) === -1) { - idSiteModel.authorizedRedirectUris.push(callbckUri); - } - - idSiteModel.save(cb); - }); - }); -} - -function revertIdSiteModel(client, idSiteApplicationHost, callbckUri, cb) { - client.getCurrentTenant(function(err, tenant) { - if (err) { - throw err; - } - - client.getResource(tenant.href + '/idSites', function(err, collection) { - if (err) { - throw err; - } - - var idSiteModel = collection.items[0]; - - idSiteModel.domainName = previousDomainName; - - idSiteModel.authorizedOriginUris = idSiteModel.authorizedOriginUris.filter(function(uri) { - return !uri.match(idSiteApplicationHost); - }); - - idSiteModel.authorizedOriginUris = idSiteModel.authorizedOriginUris.filter(function(uri) { - return !uri.match(idSiteApplicationHost.replace('https','http')); - }); - - idSiteModel.authorizedRedirectUris = idSiteModel.authorizedRedirectUris.filter(function(uri) { - return !uri.match(callbckUri); - }); - - idSiteModel.save(cb); - }); - }); -} - -function cleanup(cb){ - if(doCleanup){ - revertIdSiteModel(client,host,callbckUri,function (err) { - if (err) { - throw err; - } - console.log('ID Site Model Restored'); - cb(); - }); - } -} - -console.log(process.env.PORT); - -var tunnel = localtunnel(process.env.PORT || 9000, function(err, tunnel) { - if (err) { - console.error(err); - return process.exit(1); - } - host = tunnel.url; - console.log(host); - prepeareIdSiteModel(client,host,callbckUri,function(err){ - if (err) { - throw err; - } - console.log('ID Site Model Ready'); - setInterval(function () { },1000); - doCleanup = true; - }); - -}); - -tunnel.on('error', function (err) { - console.error(err); - cleanup(); -}); - -tunnel.on('close', cleanup); - -process.on('SIGTERM', function() { - console.log('\nCaught termination signal'); - cleanup(function (){ - process.exit(); - }); -}); - -process.on('SIGINT', function() { - console.log('\nCaught interrupt signal'); - cleanup(function (){ - process.exit(); - }); -}); \ No newline at end of file From b6e637cbddc36fe0bc74c170bc56fa5e33070dc2 Mon Sep 17 00:00:00 2001 From: Micah Silverman Date: Wed, 14 Sep 2016 09:14:30 -0400 Subject: [PATCH 16/16] Added function to wait for services to be available. --- ez_dev.sh | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/ez_dev.sh b/ez_dev.sh index 136f87e..5f58a9f 100755 --- a/ez_dev.sh +++ b/ez_dev.sh @@ -7,12 +7,34 @@ function get_property sed '/^\#/d' $FILE | grep $PROPERTY | tail -n 1 | cut -d "=" -f2- | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' } +function wait_on_service +{ + URL=$1 + for i in {1..10}; do + STATUS=`curl -s -I $URL | head -n1 | cut -d$' ' -f2` + if [ "$STATUS" == "" ]; then + STATUS=0 + fi + if [ $STATUS == "200" ]; then + break + fi + sleep 1 + done + if [ $STATUS != "200" ]; then + echo "Unable to connect to $URL." + exit 1 + fi +} + + + echo "Setting up..." echo hash npm 2>/dev/null || { echo >&2 "npm is required, but it's not installed. Install via: https://github.com/tj/n"; exit 1; } hash bower 2>/dev/null || { echo >&2 "bower is required, but it's not installed. Install via: npm install -g bower"; exit 1; } hash grunt 2>/dev/null || { echo >&2 "grunt is required, but it's not installed. Install via: npm install -g grunt-cli"; exit 1; } +hash curl 2> /dev/null || { echo >&2 "curl is required, but it's not installed. Install via: brew install curl (on mac)"; exit 1; } if [ -n "${STORMPATH_API_KEY_FILE}" ]; then STORMPATH_CLIENT_APIKEY_ID=`get_property ${STORMPATH_API_KEY_FILE} "apiKey.id"` @@ -88,6 +110,17 @@ eval $EXPR FAKESP_PID=$! cd .. +echo "Waiting for fakesep to be available..." +wait_on_service localhost:8001 + +echo "Running grunt serve..." +EXPR="grunt serve > grunt_serve.log 2>&1 &" +eval $EXPR +GRUNT_SERVE_PID=$! + +echo "Waiting for grunt server to be available..." +wait_on_service localhost:9000 + echo "Running ngrok..." cd ngrok EXPR="node ngrok.js > ../ngrok.log 2>&1 &" @@ -95,10 +128,20 @@ eval $EXPR NGROK_PID=$! cd .. -echo "Running grunt serve..." -EXPR="grunt serve > grunt_serve.log 2>&1 &" -eval $EXPR -GRUNT_SERVE_PID=$! +echo "Waiting for ngrok to be available..." +URL="" +for i in {1..10}; do + URL=`head -n1 ngrok.log` + if [[ "$URL" == *"ngrok.io"* ]]; then + break + fi + sleep 1 +done +if [ "$URL" == "" ]; then + echo "Unable to connect with ngrok." + exit 1 +fi +wait_on_service $URL echo echo "fakesp PID: $FAKESP_PID, ngrok PID: $NGROK_PID, grunt PID: $GRUNT_SERVE_PID"