From 606ddc7308c6400e5c17975c7214bd83a1ea39c7 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Wed, 15 Mar 2017 23:07:57 +0100 Subject: [PATCH] Handle SSO over multiple subdomains --- config.template.yml | 27 ++++++++++++------- example/nginx_conf/index.html | 5 ++-- example/nginx_conf/nginx.conf | 31 +++++++++++++++++++--- src/index.js | 3 +++ src/lib/routes/u2f.js | 2 +- src/lib/routes/u2f_register.js | 2 +- src/lib/server.js | 6 ++--- test/unitary/test_data_persistence.js | 2 ++ test/unitary/test_server.js | 2 ++ test/unitary/test_server_config.js | 37 +++++++++++++++++++++++++++ 10 files changed, 97 insertions(+), 20 deletions(-) create mode 100644 test/unitary/test_server_config.js diff --git a/config.template.yml b/config.template.yml index d514ba31..bba35f99 100644 --- a/config.template.yml +++ b/config.template.yml @@ -1,32 +1,41 @@ -### Level of verbosity for logs +# Level of verbosity for logs logs_level: info -### Configuration of your LDAP +# Configuration of LDAP ldap: url: ldap://ldap base_dn: ou=users,dc=example,dc=com user: cn=admin,dc=example,dc=com password: password -### Configuration of session cookies + +# Configuration of session cookies +# +# _secret_ the secret to encrypt session cookies +# _expiration_ the time before cookies expire +# _domain_ the domain to protect. +# Note: the authenticator must also be in that domain. If empty, the cookie +# is restricted to the subdomain of the issuer. session: secret: unsecure_secret expiration: 3600000 + domain: example.com -### The directory where the DB files will be saved + +# The directory where the DB files will be saved store_directory: /var/lib/auth-server/store -### Notifications are sent to users when they require a password reset, a u2f -### registration or a TOTP registration. -### Use only one available configuration: filesystem, gmail +# Notifications are sent to users when they require a password reset, a u2f +# registration or a TOTP registration. +# Use only one available configuration: filesystem, gmail notifier: - ### For testing purpose, notifications can be sent in a file + # For testing purpose, notifications can be sent in a file filesystem: filename: /var/lib/auth-server/notifications/notification.txt - ### Use your gmail account to send the notifications. You can use an app password. + # Use your gmail account to send the notifications. You can use an app password. # gmail: # username: user@example.com # password: yourpassword diff --git a/example/nginx_conf/index.html b/example/nginx_conf/index.html index 94ff081e..c59b1e20 100644 --- a/example/nginx_conf/index.html +++ b/example/nginx_conf/index.html @@ -3,7 +3,8 @@ Home page - You need to log in to access the secret!

- You can also log off by visiting the following link. + You need to log in to access the secret!

+ But you can also access it from another domain or still another one.

+ You can also log off by visiting the following link. diff --git a/example/nginx_conf/nginx.conf b/example/nginx_conf/nginx.conf index 4919f43a..5ffafad6 100644 --- a/example/nginx_conf/nginx.conf +++ b/example/nginx_conf/nginx.conf @@ -24,9 +24,7 @@ events { http { server { listen 443 ssl; - root /usr/share/nginx/html; - - server_name 127.0.0.1 localhost; + server_name auth.test.local localhost; ssl on; ssl_certificate /etc/ssl/server.crt; @@ -34,7 +32,7 @@ http { error_page 401 = @error401; location @error401 { - return 302 https://localhost:8080/authentication/login?redirect=$request_uri; + return 302 https://auth.test.local:8080/authentication/login?redirect=$scheme://$http_host$request_uri; } location /authentication/ { @@ -56,6 +54,30 @@ http { location /authentication/css/ { proxy_pass http://auth/css/; } + } + + server { + listen 443 ssl; + root /usr/share/nginx/html; + + server_name secret1.test.local secret2.test.local secret.test.local localhost; + + ssl on; + ssl_certificate /etc/ssl/server.crt; + ssl_certificate_key /etc/ssl/server.key; + + error_page 401 = @error401; + location @error401 { + return 302 https://auth.test.local:8080/authentication/login?redirect=$scheme://$http_host$request_uri; + } + + location /authentication/verify { + proxy_set_header X-Original-URI $request_uri; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + + proxy_pass http://auth/authentication/verify; + } location = /secret.html { auth_request /authentication/verify; @@ -69,3 +91,4 @@ http { } } } + diff --git a/src/index.js b/src/index.js index cb452f66..2a0a00a1 100755 --- a/src/index.js +++ b/src/index.js @@ -9,6 +9,7 @@ var u2f = require('authdog'); var nodemailer = require('nodemailer'); var nedb = require('nedb'); var YAML = require('yamljs'); +var session = require('express-session'); var config_path = process.argv[2]; if(!config_path) { @@ -27,6 +28,7 @@ var config = { ldap_users_dn: yaml_config.ldap.base_dn, ldap_user: yaml_config.ldap.user, ldap_password: yaml_config.ldap.password, + session_domain: yaml_config.session.domain, session_secret: yaml_config.session.secret, session_max_age: yaml_config.session.expiration || 3600000, // in ms store_directory: yaml_config.store_directory, @@ -48,5 +50,6 @@ deps.u2f = u2f; deps.nedb = nedb; deps.nodemailer = nodemailer; deps.ldap = ldap; +deps.session = session; server.run(config, ldap_client, deps); diff --git a/src/lib/routes/u2f.js b/src/lib/routes/u2f.js index 00e8d929..604159e2 100644 --- a/src/lib/routes/u2f.js +++ b/src/lib/routes/u2f.js @@ -35,7 +35,7 @@ function sign_request(req, res) { var u2f = req.app.get('u2f'); var meta = doc.meta; var appid = u2f_common.extract_app_id(req); - logger.info('U2F sign_request: Start authentication'); + logger.info('U2F sign_request: Start authentication to app %s', appid); return u2f.startAuthentication(appid, [meta]) }) .then(function(authRequest) { diff --git a/src/lib/routes/u2f_register.js b/src/lib/routes/u2f_register.js index 6ba17ede..5161d965 100644 --- a/src/lib/routes/u2f_register.js +++ b/src/lib/routes/u2f_register.js @@ -25,7 +25,7 @@ function register_request(req, res) { var appid = u2f_common.extract_app_id(req); logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers)); - logger.info('U2F register_request: Starting registration'); + logger.info('U2F register_request: Starting registration of app %s', appid); u2f.startRegistration(appid, []) .then(function(registrationRequest) { logger.info('U2F register_request: Sending back registration request'); diff --git a/src/lib/server.js b/src/lib/server.js index 4dbfcd7b..a828d670 100644 --- a/src/lib/server.js +++ b/src/lib/server.js @@ -7,7 +7,6 @@ var express = require('express'); var bodyParser = require('body-parser'); var speakeasy = require('speakeasy'); var path = require('path'); -var session = require('express-session'); var winston = require('winston'); var UserDataStore = require('./user_data_store'); var Notifier = require('./notifier'); @@ -28,13 +27,14 @@ function run(config, ldap_client, deps, fn) { app.use(bodyParser.json()); app.set('trust proxy', 1); // trust first proxy - app.use(session({ + app.use(deps.session({ secret: config.session_secret, resave: false, saveUninitialized: true, cookie: { secure: false, - maxAge: config.session_max_age + maxAge: config.session_max_age, + domain: config.session_domain }, })); diff --git a/test/unitary/test_data_persistence.js b/test/unitary/test_data_persistence.js index 20264104..32effd2c 100644 --- a/test/unitary/test_data_persistence.js +++ b/test/unitary/test_data_persistence.js @@ -8,6 +8,7 @@ var speakeasy = require('speakeasy'); var sinon = require('sinon'); var tmp = require('tmp'); var nedb = require('nedb'); +var session = require('express-session'); var PORT = 8050; var BASE_URL = 'http://localhost:' + PORT; @@ -88,6 +89,7 @@ describe('test data persistence', function() { deps.u2f = u2f; deps.nedb = nedb; deps.nodemailer = nodemailer; + deps.session = session; var j1 = request.jar(); var j2 = request.jar(); diff --git a/test/unitary/test_server.js b/test/unitary/test_server.js index 5e2160d5..8f6632e4 100644 --- a/test/unitary/test_server.js +++ b/test/unitary/test_server.js @@ -7,6 +7,7 @@ var assert = require('assert'); var speakeasy = require('speakeasy'); var sinon = require('sinon'); var MockDate = require('mockdate'); +var session = require('express-session'); var PORT = 8090; var BASE_URL = 'http://localhost:' + PORT; @@ -89,6 +90,7 @@ describe('test the server', function() { deps.nedb = nedb; deps.nodemailer = nodemailer; deps.ldap = ldap; + deps.session = session; _server = server.run(config, ldap_client, deps, function() { done(); diff --git a/test/unitary/test_server_config.js b/test/unitary/test_server_config.js new file mode 100644 index 00000000..1e3d74b9 --- /dev/null +++ b/test/unitary/test_server_config.js @@ -0,0 +1,37 @@ + +var sinon = require('sinon'); +var server = require('../../src/lib/server'); +var assert = require('assert'); + +describe('test server configuration', function() { + it('should set cookie scope to domain set in the config', function() { + var config = {}; + config.session_domain = 'example.com'; + config.notifier = { + gmail: { + user: 'user@example.com', + pass: 'password' + } + } + + transporter = {}; + transporter.sendMail = sinon.stub().yields(); + + var nodemailer = {}; + nodemailer.createTransport = sinon.spy(function() { + return transporter; +  }); + + var deps = {}; + deps.nedb = require('nedb'); + deps.nodemailer = nodemailer; + deps.session = sinon.spy(function() { + return function(req, res, next) { next(); }; + }); + + server.run(config, undefined, deps); + + assert(deps.session.calledOnce); + assert.equal(deps.session.getCall(0).args[0].cookie.domain, 'example.com'); + }); +});