Merge pull request #28 from clems4ever/cut-qrcode-deps

Decrease Docker image size by removing qrcode dependency and using alpine as base image
This commit is contained in:
Clément Michaud 2017-05-14 16:58:35 +02:00 committed by GitHub
commit 52b705830b
10 changed files with 153 additions and 31 deletions

2
.gitignore vendored
View File

@ -12,3 +12,5 @@ config.yml
npm-debug.log npm-debug.log
notifications/ notifications/
.vscode/

View File

@ -1,9 +1,9 @@
FROM node FROM node:7-alpine
WORKDIR /usr/src WORKDIR /usr/src
COPY package.json /usr/src/package.json COPY package.json /usr/src/package.json
RUN npm install RUN npm install --production
COPY src /usr/src COPY src /usr/src

View File

@ -9,6 +9,7 @@
"scripts": { "scripts": {
"test": "./node_modules/.bin/mocha --recursive test/unitary", "test": "./node_modules/.bin/mocha --recursive test/unitary",
"unit-test": "./node_modules/.bin/mocha --recursive test/unitary", "unit-test": "./node_modules/.bin/mocha --recursive test/unitary",
"int-test": "./node_modules/.bin/mocha --recursive test/integration",
"all-test": "./node_modules/.bin/mocha --recursive test", "all-test": "./node_modules/.bin/mocha --recursive test",
"coverage": "./node_modules/.bin/istanbul cover _mocha -- -R spec --recursive test" "coverage": "./node_modules/.bin/istanbul cover _mocha -- -R spec --recursive test"
}, },
@ -36,7 +37,6 @@
"nedb": "^1.8.0", "nedb": "^1.8.0",
"nodemailer": "^2.7.0", "nodemailer": "^2.7.0",
"object-path": "^0.11.3", "object-path": "^0.11.3",
"qrcode": "^0.5.0",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
"speakeasy": "^2.0.0", "speakeasy": "^2.0.0",
"winston": "^2.3.1", "winston": "^2.3.1",

View File

@ -1,6 +1,5 @@
var objectPath = require('object-path'); var objectPath = require('object-path');
var Promise = require('bluebird'); var Promise = require('bluebird');
var QRCode = require('qrcode');
var CHALLENGE = 'totp-register'; var CHALLENGE = 'totp-register';
@ -16,7 +15,6 @@ module.exports = {
post: post, post: post,
} }
function pre_check(req) { function pre_check(req) {
var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor'); var first_factor_passed = objectPath.get(req, 'session.auth_session.first_factor');
if(!first_factor_passed) { if(!first_factor_passed) {
@ -36,19 +34,6 @@ function pre_check(req) {
return Promise.resolve(identity); return Promise.resolve(identity);
} }
function secretToDataURLAsync(secret) {
return new Promise(function(resolve, reject) {
QRCode.toDataURL(secret.otpauth_url, function(err, url_data) {
if(err) {
reject(err);
return;
}
resolve(url_data);
});
});
}
// Generate a secret and send it to the user // Generate a secret and send it to the user
function post(req, res) { function post(req, res) {
var logger = req.app.get('logger'); var logger = req.app.get('logger');
@ -64,17 +49,12 @@ function post(req, res) {
var user_data_store = req.app.get('user data store'); var user_data_store = req.app.get('user data store');
var totp = req.app.get('totp engine'); var totp = req.app.get('totp engine');
var secret = totp.generateSecret(); var secret = totp.generateSecret();
var qrcode_data;
secretToDataURLAsync(secret) logger.debug('POST new-totp-secret: save the TOTP secret in DB');
.then(function(data) { user_data_store.set_totp_secret(userid, secret)
qrcode_data = data;
logger.debug('POST new-totp-secret: save the TOTP secret in DB');
return user_data_store.set_totp_secret(userid, secret);
})
.then(function() { .then(function() {
var doc = {}; var doc = {};
doc.qrcode = qrcode_data; doc.otpauth_url = secret.otpauth_url;
doc.base32 = secret.base32; doc.base32 = secret.base32;
doc.ascii = secret.ascii; doc.ascii = secret.ascii;

View File

@ -57,7 +57,12 @@ p { color: #fff; text-shadow: 0 0 10px rgba(0,0,0,0.3); letter-spacing:1px; text
a { color: #fff; text-align: center; } a { color: #fff; text-align: center; }
#qrcode { text-align: center; } #qrcode img {
margin: auto;
text-align: center;
padding: 10px;
background: white;
}
#secret { font-size: 0.7em; } #secret { font-size: 0.7em; }

1
src/public_html/js/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -19,9 +19,9 @@ function generateSecret(fn) {
} }
function onSecretGenerated(err, secret) { function onSecretGenerated(err, secret) {
// console.log('secret generated successfully', secret); console.log('secret generated successfully', secret);
var img = $('<img src="' + secret.qrcode + '" alt="secret-qrcode"/>'); console.log('OTP Auth URL=', secret.otpauth_url);
$('#qrcode').append(img); new QRCode(document.getElementById("qrcode"), secret.otpauth_url);
$("#secret").text(secret.base32); $("#secret").text(secret.base32);
} }

View File

@ -14,5 +14,6 @@
</body> </body>
<% include scripts %> <% include scripts %>
<script src="js/qrcode.min.js"></script>
<script src="js/totp-register.js"></script> <script src="js/totp-register.js"></script>
</html> </html>

View File

@ -0,0 +1,133 @@
var request_ = require('request');
var assert = require('assert');
var speakeasy = require('speakeasy');
var j = request_.jar();
var Promise = require('bluebird');
var request = Promise.promisifyAll(request_.defaults({jar: j}));
var util = require('util');
var sinon = require('sinon');
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
var AUTHELIA_HOST = 'nginx';
var DOMAIN = 'test.local';
var PORT = 8080;
var BASE_URL = util.format('https://%s.%s:%d', 'home', DOMAIN, PORT);
var BASE_AUTH_URL = util.format('https://%s.%s:%d/authentication', 'auth', DOMAIN, PORT);
describe('test the server', function() {
var home_page;
var login_page;
before(function() {
var home_page_promise = getHomePage()
.then(function(data) {
home_page = data.body;
});
var login_page_promise = getLoginPage()
.then(function(data) {
login_page = data.body;
});
return Promise.all([home_page_promise,
login_page_promise]);
});
it('should serve the login page', function(done) {
getPromised(BASE_AUTH_URL + '/login?redirect=/')
.then(function(data) {
assert.equal(data.statusCode, 200);
done();
});
});
it('should serve the homepage', function(done) {
getPromised(BASE_URL + '/')
.then(function(data) {
assert.equal(data.statusCode, 200);
done();
});
});
it('should redirect when logout', function(done) {
getPromised(BASE_AUTH_URL + '/logout?redirect=' + BASE_URL)
.then(function(data) {
assert.equal(data.statusCode, 200);
assert.equal(data.body, home_page);
done();
});
});
it('should be redirected to the login page when accessing secret while not authenticated', function(done) {
var url = BASE_URL + '/secret.html';
// console.log(url);
getPromised(url)
.then(function(data) {
assert.equal(data.statusCode, 200);
assert.equal(data.body, login_page);
done();
});
});
it.skip('should fail the first factor', function(done) {
postPromised(BASE_AUTH_URL + '/1stfactor', {
form: {
username: 'admin',
password: 'password',
}
})
.then(function(data) {
assert.equal(data.body, 'Bad credentials');
done();
});
});
function login_as(username, password) {
return postPromised(BASE_AUTH_URL + '/1stfactor', {
form: {
username: 'john',
password: 'password',
}
})
.then(function(data) {
assert.equal(data.statusCode, 204);
return Promise.resolve();
});
}
it('should succeed the first factor', function() {
return login_as('john', 'password');
});
describe('test ldap connection', function() {
it('should not fail after inactivity', function() {
var clock = sinon.useFakeTimers();
return login_as('john', 'password')
.then(function() {
clock.tick(3600000 * 24); // 24 hour
return login_as('john', 'password');
})
.then(function() {
clock.restore();
return Promise.resolve();
});
});
});
});
function getPromised(url) {
return request.getAsync(url);
}
function postPromised(url, body) {
return request.postAsync(url, body);
}
function getHomePage() {
return getPromised(BASE_URL + '/');
}
function getLoginPage() {
return getPromised(BASE_AUTH_URL + '/login');
}

View File

@ -118,7 +118,7 @@ describe('test totp register', function() {
req.session.auth_session.identity_check = {}; req.session.auth_session.identity_check = {};
req.session.auth_session.identity_check.userid = 'user'; req.session.auth_session.identity_check.userid = 'user';
req.session.auth_session.identity_check.challenge = 'totp-register'; req.session.auth_session.identity_check.challenge = 'totp-register';
user_data_store.set_totp_secret.throws('internal error'); user_data_store.set_totp_secret.returns(new Promise.reject('internal error'));
res.send = sinon.spy(function() { res.send = sinon.spy(function() {
assert.equal(res.status.getCall(0).args[0], 500); assert.equal(res.status.getCall(0).args[0], 500);