commit
0d494055ac
|
@ -35,13 +35,20 @@ Otherwise here are the available steps to deploy on your machine.
|
||||||
The provided example is docker-based so that you can deploy and test it very
|
The provided example is docker-based so that you can deploy and test it very
|
||||||
quickly. First clone the repo make sure you don't have anything listening on
|
quickly. First clone the repo make sure you don't have anything listening on
|
||||||
port 8080 before starting.
|
port 8080 before starting.
|
||||||
|
Add the following lines to your /etc/hosts to simulate multiple subdomains
|
||||||
|
|
||||||
|
127.0.0.1 secret.test.local
|
||||||
|
127.0.0.1 secret1.test.local
|
||||||
|
127.0.0.1 secret2.test.local
|
||||||
|
127.0.0.1 auth.test.local
|
||||||
|
|
||||||
Then, type the following command to build and deploy the services:
|
Then, type the following command to build and deploy the services:
|
||||||
|
|
||||||
docker-compose build
|
docker-compose build
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
|
|
||||||
After few seconds the services should be running and you should be able to visit
|
After few seconds the services should be running and you should be able to visit
|
||||||
[https://localhost:8080/](https://localhost:8080/).
|
[https://localhost:8080/](https://secret.test.local:8080/).
|
||||||
|
|
||||||
Normally, a self-signed certificate exception should appear, it has to be
|
Normally, a self-signed certificate exception should appear, it has to be
|
||||||
accepted before getting to the login page:
|
accepted before getting to the login page:
|
||||||
|
|
|
@ -1,32 +1,41 @@
|
||||||
|
|
||||||
### Level of verbosity for logs
|
# Level of verbosity for logs
|
||||||
logs_level: info
|
logs_level: info
|
||||||
|
|
||||||
### Configuration of your LDAP
|
# Configuration of LDAP
|
||||||
ldap:
|
ldap:
|
||||||
url: ldap://ldap
|
url: ldap://ldap
|
||||||
base_dn: ou=users,dc=example,dc=com
|
base_dn: ou=users,dc=example,dc=com
|
||||||
user: cn=admin,dc=example,dc=com
|
user: cn=admin,dc=example,dc=com
|
||||||
password: password
|
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:
|
session:
|
||||||
secret: unsecure_secret
|
secret: unsecure_secret
|
||||||
expiration: 3600000
|
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
|
store_directory: /var/lib/auth-server/store
|
||||||
|
|
||||||
|
|
||||||
### Notifications are sent to users when they require a password reset, a u2f
|
# Notifications are sent to users when they require a password reset, a u2f
|
||||||
### registration or a TOTP registration.
|
# registration or a TOTP registration.
|
||||||
### Use only one available configuration: filesystem, gmail
|
# Use only one available configuration: filesystem, gmail
|
||||||
notifier:
|
notifier:
|
||||||
### For testing purpose, notifications can be sent in a file
|
# For testing purpose, notifications can be sent in a file
|
||||||
filesystem:
|
filesystem:
|
||||||
filename: /var/lib/auth-server/notifications/notification.txt
|
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:
|
# gmail:
|
||||||
# username: user@example.com
|
# username: user@example.com
|
||||||
# password: yourpassword
|
# password: yourpassword
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
<title>Home page</title>
|
<title>Home page</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
You need to <a href="/authentication/login?redirect=/">log in</a> to access the <a href="/secret.html">secret</a>!<br/><br/>
|
You need to <a href="https://auth.test.local:8080/authentication/login?redirect=https://secret.test.local:8080/">log in</a> to access the <a href="/secret.html">secret</a>!<br/><br/>
|
||||||
You can also log off by visiting the following <a href="/authentication/logout?redirect=/">link</a>.
|
But you can also access it from another <a href="https://secret1.test.local:8080/secret.html">domain</a> or still <a href="https://secret2.test.local:8080/secret.html">another one</a>.<br/><br/>
|
||||||
|
You can also log off by visiting the following <a href="https://auth.test.local:8080/authentication/logout?redirect=https://secret.test.local:8080/">link</a>.
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -24,9 +24,7 @@ events {
|
||||||
http {
|
http {
|
||||||
server {
|
server {
|
||||||
listen 443 ssl;
|
listen 443 ssl;
|
||||||
root /usr/share/nginx/html;
|
server_name auth.test.local localhost;
|
||||||
|
|
||||||
server_name 127.0.0.1 localhost;
|
|
||||||
|
|
||||||
ssl on;
|
ssl on;
|
||||||
ssl_certificate /etc/ssl/server.crt;
|
ssl_certificate /etc/ssl/server.crt;
|
||||||
|
@ -34,7 +32,7 @@ http {
|
||||||
|
|
||||||
error_page 401 = @error401;
|
error_page 401 = @error401;
|
||||||
location @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/ {
|
location /authentication/ {
|
||||||
|
@ -56,6 +54,30 @@ http {
|
||||||
location /authentication/css/ {
|
location /authentication/css/ {
|
||||||
proxy_pass http://auth/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 {
|
location = /secret.html {
|
||||||
auth_request /authentication/verify;
|
auth_request /authentication/verify;
|
||||||
|
@ -69,3 +91,4 @@ http {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ var u2f = require('authdog');
|
||||||
var nodemailer = require('nodemailer');
|
var nodemailer = require('nodemailer');
|
||||||
var nedb = require('nedb');
|
var nedb = require('nedb');
|
||||||
var YAML = require('yamljs');
|
var YAML = require('yamljs');
|
||||||
|
var session = require('express-session');
|
||||||
|
|
||||||
var config_path = process.argv[2];
|
var config_path = process.argv[2];
|
||||||
if(!config_path) {
|
if(!config_path) {
|
||||||
|
@ -27,6 +28,7 @@ var config = {
|
||||||
ldap_users_dn: yaml_config.ldap.base_dn,
|
ldap_users_dn: yaml_config.ldap.base_dn,
|
||||||
ldap_user: yaml_config.ldap.user,
|
ldap_user: yaml_config.ldap.user,
|
||||||
ldap_password: yaml_config.ldap.password,
|
ldap_password: yaml_config.ldap.password,
|
||||||
|
session_domain: yaml_config.session.domain,
|
||||||
session_secret: yaml_config.session.secret,
|
session_secret: yaml_config.session.secret,
|
||||||
session_max_age: yaml_config.session.expiration || 3600000, // in ms
|
session_max_age: yaml_config.session.expiration || 3600000, // in ms
|
||||||
store_directory: yaml_config.store_directory,
|
store_directory: yaml_config.store_directory,
|
||||||
|
@ -48,5 +50,6 @@ deps.u2f = u2f;
|
||||||
deps.nedb = nedb;
|
deps.nedb = nedb;
|
||||||
deps.nodemailer = nodemailer;
|
deps.nodemailer = nodemailer;
|
||||||
deps.ldap = ldap;
|
deps.ldap = ldap;
|
||||||
|
deps.session = session;
|
||||||
|
|
||||||
server.run(config, ldap_client, deps);
|
server.run(config, ldap_client, deps);
|
||||||
|
|
|
@ -109,8 +109,12 @@ function identity_check_post(endpoint, icheck_interface) {
|
||||||
throw new exceptions.AccessDeniedError();
|
throw new exceptions.AccessDeniedError();
|
||||||
})
|
})
|
||||||
.then(function(token) {
|
.then(function(token) {
|
||||||
|
var redirect_url = objectPath.get(req, 'body.redirect');
|
||||||
var original_url = util.format('https://%s%s', req.headers.host, req.headers['x-original-uri']);
|
var original_url = util.format('https://%s%s', req.headers.host, req.headers['x-original-uri']);
|
||||||
var link_url = util.format('%s?identity_token=%s', original_url, token);
|
var link_url = util.format('%s?identity_token=%s', original_url, token);
|
||||||
|
if(redirect_url) {
|
||||||
|
link_url = util.format('%s&redirect=%s', link_url, redirect_url);
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('POST identity_check: notify to %s', identity.userid);
|
logger.info('POST identity_check: notify to %s', identity.userid);
|
||||||
return notifier.notify(identity, icheck_interface.email_subject, link_url);
|
return notifier.notify(identity, icheck_interface.email_subject, link_url);
|
||||||
|
|
|
@ -35,7 +35,7 @@ function sign_request(req, res) {
|
||||||
var u2f = req.app.get('u2f');
|
var u2f = req.app.get('u2f');
|
||||||
var meta = doc.meta;
|
var meta = doc.meta;
|
||||||
var appid = u2f_common.extract_app_id(req);
|
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])
|
return u2f.startAuthentication(appid, [meta])
|
||||||
})
|
})
|
||||||
.then(function(authRequest) {
|
.then(function(authRequest) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ function register_request(req, res) {
|
||||||
var appid = u2f_common.extract_app_id(req);
|
var appid = u2f_common.extract_app_id(req);
|
||||||
|
|
||||||
logger.debug('U2F register_request: headers=%s', JSON.stringify(req.headers));
|
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, [])
|
u2f.startRegistration(appid, [])
|
||||||
.then(function(registrationRequest) {
|
.then(function(registrationRequest) {
|
||||||
logger.info('U2F register_request: Sending back registration request');
|
logger.info('U2F register_request: Sending back registration request');
|
||||||
|
|
|
@ -7,7 +7,6 @@ var express = require('express');
|
||||||
var bodyParser = require('body-parser');
|
var bodyParser = require('body-parser');
|
||||||
var speakeasy = require('speakeasy');
|
var speakeasy = require('speakeasy');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var session = require('express-session');
|
|
||||||
var winston = require('winston');
|
var winston = require('winston');
|
||||||
var UserDataStore = require('./user_data_store');
|
var UserDataStore = require('./user_data_store');
|
||||||
var Notifier = require('./notifier');
|
var Notifier = require('./notifier');
|
||||||
|
@ -28,13 +27,14 @@ function run(config, ldap_client, deps, fn) {
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.set('trust proxy', 1); // trust first proxy
|
app.set('trust proxy', 1); // trust first proxy
|
||||||
|
|
||||||
app.use(session({
|
app.use(deps.session({
|
||||||
secret: config.session_secret,
|
secret: config.session_secret,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: true,
|
saveUninitialized: true,
|
||||||
cookie: {
|
cookie: {
|
||||||
secure: false,
|
secure: false,
|
||||||
maxAge: config.session_max_age
|
maxAge: config.session_max_age,
|
||||||
|
domain: config.session_domain
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,11 @@
|
||||||
params={};
|
params={};
|
||||||
location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi,function(s,k,v){params[k]=v});
|
location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi,function(s,k,v){params[k]=v});
|
||||||
|
|
||||||
|
function get_redirect_param() {
|
||||||
|
if('redirect' in params)
|
||||||
|
return params['redirect'];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
function setupEnterKeypressListener(filter, fn) {
|
function setupEnterKeypressListener(filter, fn) {
|
||||||
$(filter).on('keydown', 'input', function (e) {
|
$(filter).on('keydown', 'input', function (e) {
|
||||||
|
@ -49,7 +54,12 @@ function onTotpSignButtonClicked() {
|
||||||
function onTotpRegisterButtonClicked() {
|
function onTotpRegisterButtonClicked() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: '/authentication/totp-register'
|
url: '/authentication/totp-register',
|
||||||
|
data: JSON.stringify({
|
||||||
|
redirect: get_redirect_param()
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
})
|
})
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
$.notify('An email has been sent to your email address', 'info');
|
$.notify('An email has been sent to your email address', 'info');
|
||||||
|
@ -82,7 +92,12 @@ function onU2fRegistrationButtonClicked() {
|
||||||
function askForU2fRegistration(fn) {
|
function askForU2fRegistration(fn) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: '/authentication/u2f-register'
|
url: '/authentication/u2f-register',
|
||||||
|
data: JSON.stringify({
|
||||||
|
redirect: get_redirect_param()
|
||||||
|
}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
dataType: 'json',
|
||||||
})
|
})
|
||||||
.done(function(data) {
|
.done(function(data) {
|
||||||
fn(undefined, data);
|
fn(undefined, data);
|
||||||
|
@ -158,6 +173,7 @@ function validateFirstFactor(username, password, fn) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function redirect() {
|
function redirect() {
|
||||||
var redirect_uri = '/';
|
var redirect_uri = '/';
|
||||||
if('redirect' in params) {
|
if('redirect' in params) {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
|
params={};
|
||||||
|
location.search.replace(/[?&]+([^=&]+)=([^&]*)/gi,function(s,k,v){params[k]=v});
|
||||||
|
|
||||||
function generateSecret(fn) {
|
function generateSecret(fn) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
|
@ -22,7 +25,18 @@ function onSecretGenerated(err, secret) {
|
||||||
$("#secret").text(secret.base32);
|
$("#secret").text(secret.base32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function redirect() {
|
||||||
|
var redirect_uri = '/authentication/login';
|
||||||
|
if('redirect' in params) {
|
||||||
|
redirect_uri = params['redirect'];
|
||||||
|
}
|
||||||
|
window.location.replace(redirect_uri);
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
generateSecret(onSecretGenerated);
|
generateSecret(onSecretGenerated);
|
||||||
|
$('#login-button').on('click', function() {
|
||||||
|
redirect();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -39,7 +39,7 @@ function startRegister(fn, timeout) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirect() {
|
function redirect() {
|
||||||
var redirect_uri = '/';
|
var redirect_uri = '/authentication/login';
|
||||||
if('redirect' in params) {
|
if('redirect' in params) {
|
||||||
redirect_uri = params['redirect'];
|
redirect_uri = params['redirect'];
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<p>Insert your secret in Google Authenticator</p>
|
<p>Insert your secret in Google Authenticator</p>
|
||||||
<p id="secret"></p>
|
<p id="secret"></p>
|
||||||
<div id="qrcode"></div>
|
<div id="qrcode"></div>
|
||||||
<p><a href="/authentication/login">Login</a></p>
|
<p><a href="#" id="login-button">Login</a></p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ var speakeasy = require('speakeasy');
|
||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var tmp = require('tmp');
|
var tmp = require('tmp');
|
||||||
var nedb = require('nedb');
|
var nedb = require('nedb');
|
||||||
|
var session = require('express-session');
|
||||||
|
|
||||||
var PORT = 8050;
|
var PORT = 8050;
|
||||||
var BASE_URL = 'http://localhost:' + PORT;
|
var BASE_URL = 'http://localhost:' + PORT;
|
||||||
|
@ -88,6 +89,7 @@ describe('test data persistence', function() {
|
||||||
deps.u2f = u2f;
|
deps.u2f = u2f;
|
||||||
deps.nedb = nedb;
|
deps.nedb = nedb;
|
||||||
deps.nodemailer = nodemailer;
|
deps.nodemailer = nodemailer;
|
||||||
|
deps.session = session;
|
||||||
|
|
||||||
var j1 = request.jar();
|
var j1 = request.jar();
|
||||||
var j2 = request.jar();
|
var j2 = request.jar();
|
||||||
|
|
|
@ -7,6 +7,7 @@ var assert = require('assert');
|
||||||
var speakeasy = require('speakeasy');
|
var speakeasy = require('speakeasy');
|
||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
var MockDate = require('mockdate');
|
var MockDate = require('mockdate');
|
||||||
|
var session = require('express-session');
|
||||||
|
|
||||||
var PORT = 8090;
|
var PORT = 8090;
|
||||||
var BASE_URL = 'http://localhost:' + PORT;
|
var BASE_URL = 'http://localhost:' + PORT;
|
||||||
|
@ -89,6 +90,7 @@ describe('test the server', function() {
|
||||||
deps.nedb = nedb;
|
deps.nedb = nedb;
|
||||||
deps.nodemailer = nodemailer;
|
deps.nodemailer = nodemailer;
|
||||||
deps.ldap = ldap;
|
deps.ldap = ldap;
|
||||||
|
deps.session = session;
|
||||||
|
|
||||||
_server = server.run(config, ldap_client, deps, function() {
|
_server = server.run(config, ldap_client, deps, function() {
|
||||||
done();
|
done();
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue