From fad23ff3beceac928f7fdb968de37608d15b8286 Mon Sep 17 00:00:00 2001 From: Clement Michaud Date: Sun, 21 May 2017 12:27:12 +0200 Subject: [PATCH] Move Authentication validator and routes to typescript --- src/lib/routes.js | 39 ------- src/lib/routes.ts | 41 +++++++ src/lib/routes/AuthenticationValidator.ts | 43 +++++++ src/lib/routes/verify.js | 44 ------- test/unitary/mocks/express.ts | 68 ++++++++++- .../routes/AuthenticationValidator.test.ts | 110 ++++++++++++++++++ test/unitary/routes/test_verify.js | 101 ---------------- 7 files changed, 261 insertions(+), 185 deletions(-) delete mode 100644 src/lib/routes.js create mode 100644 src/lib/routes.ts create mode 100644 src/lib/routes/AuthenticationValidator.ts delete mode 100644 src/lib/routes/verify.js create mode 100644 test/unitary/routes/AuthenticationValidator.test.ts delete mode 100644 test/unitary/routes/test_verify.js diff --git a/src/lib/routes.js b/src/lib/routes.js deleted file mode 100644 index b2c9f3b2..00000000 --- a/src/lib/routes.js +++ /dev/null @@ -1,39 +0,0 @@ - -var first_factor = require('./routes/FirstFactor'); -var second_factor = require('./routes/second_factor'); -var reset_password = require('./routes/reset_password'); -var verify = require('./routes/verify'); -var u2f_register_handler = require('./routes/u2f_register_handler'); -var totp_register = require('./routes/totp_register'); -var objectPath = require('object-path'); - -module.exports = { - login: serveLogin, - logout: serveLogout, - verify: verify, - first_factor: first_factor, - second_factor: second_factor, - reset_password: reset_password, - u2f_register: u2f_register_handler, - totp_register: totp_register, -} - -function serveLogin(req, res) { - if(!(objectPath.has(req, 'session.auth_session'))) { - req.session.auth_session = {}; - req.session.auth_session.first_factor = false; - req.session.auth_session.second_factor = false; - } - res.render('login'); -} - -function serveLogout(req, res) { - var redirect_param = req.query.redirect; - var redirect_url = redirect_param || '/'; - req.session.auth_session = { - first_factor: false, - second_factor: false - } - res.redirect(redirect_url); -} - diff --git a/src/lib/routes.ts b/src/lib/routes.ts new file mode 100644 index 00000000..1446fa59 --- /dev/null +++ b/src/lib/routes.ts @@ -0,0 +1,41 @@ + +import FirstFactor = require("./routes/FirstFactor"); +import second_factor = require("./routes/second_factor"); +import reset_password = require("./routes/reset_password"); +import AuthenticationValidator = require("./routes/AuthenticationValidator"); +import u2f_register_handler = require("./routes/u2f_register_handler"); +import totp_register = require("./routes/totp_register"); +import objectPath = require("object-path"); + +import express = require("express"); + +export = { + login: serveLogin, + logout: serveLogout, + verify: AuthenticationValidator, + first_factor: FirstFactor, + second_factor: second_factor, + reset_password: reset_password, + u2f_register: u2f_register_handler, + totp_register: totp_register, +}; + +function serveLogin(req: express.Request, res: express.Response) { + if (!(objectPath.has(req, "session.auth_session"))) { + req.session.auth_session = {}; + req.session.auth_session.first_factor = false; + req.session.auth_session.second_factor = false; + } + res.render("login"); +} + +function serveLogout(req: express.Request, res: express.Response) { + const redirect_param = req.query.redirect; + const redirect_url = redirect_param || "/"; + req.session.auth_session = { + first_factor: false, + second_factor: false + }; + res.redirect(redirect_url); +} + diff --git a/src/lib/routes/AuthenticationValidator.ts b/src/lib/routes/AuthenticationValidator.ts new file mode 100644 index 00000000..38b861da --- /dev/null +++ b/src/lib/routes/AuthenticationValidator.ts @@ -0,0 +1,43 @@ + +import objectPath = require("object-path"); +import BluebirdPromise = require("bluebird"); +import express = require("express"); + +function verify_filter(req: express.Request, res: express.Response) { + const logger = req.app.get("logger"); + + if (!objectPath.has(req, "session.auth_session")) + return BluebirdPromise.reject("No auth_session variable"); + + if (!objectPath.has(req, "session.auth_session.first_factor")) + return BluebirdPromise.reject("No first factor variable"); + + if (!objectPath.has(req, "session.auth_session.second_factor")) + return BluebirdPromise.reject("No second factor variable"); + + if (!objectPath.has(req, "session.auth_session.userid")) + return BluebirdPromise.reject("No userid variable"); + + const host = objectPath.get(req, "headers.host"); + const domain = host.split(":")[0]; + + if (!req.session.auth_session.first_factor || + !req.session.auth_session.second_factor) + return BluebirdPromise.reject("First or second factor not validated"); + + return BluebirdPromise.resolve(); +} + +export = function(req: express.Request, res: express.Response) { + verify_filter(req, res) + .then(function () { + res.status(204); + res.send(); + }) + .catch(function (err) { + req.app.get("logger").error(err); + res.status(401); + res.send(); + }); +}; + diff --git a/src/lib/routes/verify.js b/src/lib/routes/verify.js deleted file mode 100644 index 1b40649b..00000000 --- a/src/lib/routes/verify.js +++ /dev/null @@ -1,44 +0,0 @@ - -module.exports = verify; - -var objectPath = require('object-path'); -var BluebirdPromise = require('bluebird'); - -function verify_filter(req, res) { - var logger = req.app.get('logger'); - - if(!objectPath.has(req, 'session.auth_session')) - return BluebirdPromise.reject('No auth_session variable'); - - if(!objectPath.has(req, 'session.auth_session.first_factor')) - return BluebirdPromise.reject('No first factor variable'); - - if(!objectPath.has(req, 'session.auth_session.second_factor')) - return BluebirdPromise.reject('No second factor variable'); - - if(!objectPath.has(req, 'session.auth_session.userid')) - return BluebirdPromise.reject('No userid variable'); - - var host = objectPath.get(req, 'headers.host'); - var domain = host.split(':')[0]; - - if(!req.session.auth_session.first_factor || - !req.session.auth_session.second_factor) - return BluebirdPromise.reject('First or second factor not validated'); - - return BluebirdPromise.resolve(); -} - -function verify(req, res) { - verify_filter(req, res) - .then(function() { - res.status(204); - res.send(); - }) - .catch(function(err) { - req.app.get('logger').error(err); - res.status(401); - res.send(); - }); -} - diff --git a/test/unitary/mocks/express.ts b/test/unitary/mocks/express.ts index 31aec299..009cb4a3 100644 --- a/test/unitary/mocks/express.ts +++ b/test/unitary/mocks/express.ts @@ -1,17 +1,51 @@ import sinon = require("sinon"); +import express = require("express"); export interface RequestMock { app?: any; body?: any; session?: any; headers?: any; + get?: any; } export interface ResponseMock { send: sinon.SinonStub | sinon.SinonSpy; + sendStatus: sinon.SinonStub; + sendFile: sinon.SinonStub; + sendfile: sinon.SinonStub; status: sinon.SinonStub; json: sinon.SinonStub; + links: sinon.SinonStub; + jsonp: sinon.SinonStub; + download: sinon.SinonStub; + contentType: sinon.SinonStub; + type: sinon.SinonStub; + format: sinon.SinonStub; + attachment: sinon.SinonStub; + set: sinon.SinonStub; + header: sinon.SinonStub; + headersSent: boolean; + get: sinon.SinonStub; + clearCookie: sinon.SinonStub; + cookie: sinon.SinonStub; + location: sinon.SinonStub; + redirect: sinon.SinonStub; + render: sinon.SinonStub; + locals: sinon.SinonStub; + charset: string; + vary: sinon.SinonStub; + app: any; + write: sinon.SinonStub; + writeContinue: sinon.SinonStub; + writeHead: sinon.SinonStub; + statusCode: number; + statusMessage: string; + setHeader: sinon.SinonStub; + setTimeout: sinon.SinonStub; + sendDate: boolean; + getHeader: sinon.SinonStub; } export function RequestMock(): RequestMock { @@ -25,6 +59,38 @@ export function ResponseMock(): ResponseMock { return { send: sinon.stub(), status: sinon.stub(), - json: sinon.stub() + json: sinon.stub(), + sendStatus: sinon.stub(), + links: sinon.stub(), + jsonp: sinon.stub(), + sendFile: sinon.stub(), + sendfile: sinon.stub(), + download: sinon.stub(), + contentType: sinon.stub(), + type: sinon.stub(), + format: sinon.stub(), + attachment: sinon.stub(), + set: sinon.stub(), + header: sinon.stub(), + headersSent: true, + get: sinon.stub(), + clearCookie: sinon.stub(), + cookie: sinon.stub(), + location: sinon.stub(), + redirect: sinon.stub(), + render: sinon.stub(), + locals: sinon.stub(), + charset: "utf-8", + vary: sinon.stub(), + app: sinon.stub(), + write: sinon.stub(), + writeContinue: sinon.stub(), + writeHead: sinon.stub(), + statusCode: 200, + statusMessage: "message", + setHeader: sinon.stub(), + setTimeout: sinon.stub(), + sendDate: true, + getHeader: sinon.stub() }; } diff --git a/test/unitary/routes/AuthenticationValidator.test.ts b/test/unitary/routes/AuthenticationValidator.test.ts new file mode 100644 index 00000000..8d54c359 --- /dev/null +++ b/test/unitary/routes/AuthenticationValidator.test.ts @@ -0,0 +1,110 @@ + +import assert = require("assert"); +import AuthenticationValidator = require("../../../src/lib/routes/AuthenticationValidator"); +import sinon = require("sinon"); +import winston = require("winston"); + +import express = require("express"); + +import ExpressMock = require("../mocks/express"); +import AccessControllerMock = require("../mocks/AccessController"); + +describe("test authentication token verification", function () { + let req: ExpressMock.RequestMock; + let res: ExpressMock.ResponseMock; + let accessController: AccessControllerMock.AccessControllerMock; + + beforeEach(function () { + accessController = AccessControllerMock.AccessControllerMock(); + accessController.isDomainAllowedForUser.returns(true); + + req = ExpressMock.RequestMock(); + res = ExpressMock.ResponseMock(); + req.headers = {}; + req.headers.host = "secret.example.com"; + req.app.get = sinon.stub(); + req.app.get.withArgs("config").returns({}); + req.app.get.withArgs("logger").returns(winston); + req.app.get.withArgs("access controller").returns(accessController); + }); + + interface AuthenticationSession { + first_factor?: boolean; + second_factor?: boolean; + userid?: string; + groups?: string[]; + } + + it("should be already authenticated", function (done) { + req.session = {}; + req.session.auth_session = { + first_factor: true, + second_factor: true, + userid: "myuser", + } as AuthenticationSession; + + res.send = sinon.spy(function () { + assert.equal(204, res.status.getCall(0).args[0]); + done(); + }); + + AuthenticationValidator(req as express.Request, res as any); + }); + + describe("given different cases of session", function () { + function test_session(auth_session: AuthenticationSession, status_code: number) { + return new Promise(function (resolve, reject) { + req.session = {}; + req.session.auth_session = auth_session; + + res.send = sinon.spy(function () { + assert.equal(status_code, res.status.getCall(0).args[0]); + resolve(); + }); + + AuthenticationValidator(req as express.Request, res as any); + }); + } + + function test_unauthorized(auth_session: AuthenticationSession) { + return test_session(auth_session, 401); + } + + function test_authorized(auth_session: AuthenticationSession) { + return test_session(auth_session, 204); + } + + it("should not be authenticated when second factor is missing", function () { + return test_unauthorized({ + userid: "user", + first_factor: true, + second_factor: false + }); + }); + + it("should not be authenticated when first factor is missing", function () { + return test_unauthorized({ first_factor: false, second_factor: true }); + }); + + it("should not be authenticated when userid is missing", function () { + return test_unauthorized({ + first_factor: true, + second_factor: true, + groups: ["mygroup"], + }); + }); + + it("should not be authenticated when first and second factor are missing", function () { + return test_unauthorized({ first_factor: false, second_factor: false }); + }); + + it("should not be authenticated when session has not be initiated", function () { + return test_unauthorized(undefined); + }); + + it("should not be authenticated when session is partially initialized", function () { + return test_unauthorized({ first_factor: true }); + }); + }); +}); + diff --git a/test/unitary/routes/test_verify.js b/test/unitary/routes/test_verify.js deleted file mode 100644 index a3b7710a..00000000 --- a/test/unitary/routes/test_verify.js +++ /dev/null @@ -1,101 +0,0 @@ - -var assert = require('assert'); -var verify = require('../../../src/lib/routes/verify'); -var sinon = require('sinon'); -var winston = require('winston'); - -describe('test authentication token verification', function() { - var req, res; - var config_mock; - var acl_matcher; - - beforeEach(function() { - acl_matcher = { - is_domain_allowed: sinon.stub().returns(true) - }; - var access_control = { - matcher: acl_matcher - } - config_mock = {}; - req = {}; - res = {}; - req.headers = {}; - req.headers.host = 'secret.example.com'; - req.app = {}; - req.app.get = sinon.stub(); - req.app.get.withArgs('config').returns(config_mock); - req.app.get.withArgs('logger').returns(winston); - req.app.get.withArgs('access control').returns(access_control); - res.status = sinon.spy(); - }); - - it('should be already authenticated', function(done) { - req.session = {}; - req.session.auth_session = { - first_factor: true, - second_factor: true, - userid: 'myuser', - allowed_domains: ['*'] - }; - - res.send = sinon.spy(function() { - assert.equal(204, res.status.getCall(0).args[0]); - done(); - }); - - verify(req, res); - }); - - describe('given different cases of session', function() { - function test_session(auth_session, status_code) { - return new Promise(function(resolve, reject) { - req.session = {}; - req.session.auth_session = auth_session; - - res.send = sinon.spy(function() { - assert.equal(status_code, res.status.getCall(0).args[0]); - resolve(); - }); - - verify(req, res); - }); - } - - function test_unauthorized(auth_session) { - return test_session(auth_session, 401); - } - - function test_authorized(auth_session) { - return test_session(auth_session, 204); - } - - it('should not be authenticated when second factor is missing', function() { - return test_unauthorized({ first_factor: true, second_factor: false }); - }); - - it('should not be authenticated when first factor is missing', function() { - return test_unauthorized({ first_factor: false, second_factor: true }); - }); - - it('should not be authenticated when userid is missing', function() { - return test_unauthorized({ - first_factor: true, - second_factor: true, - group: 'mygroup', - }); - }); - - it('should not be authenticated when first and second factor are missing', function() { - return test_unauthorized({ first_factor: false, second_factor: false }); - }); - - it('should not be authenticated when session has not be initiated', function() { - return test_unauthorized(undefined); - }); - - it('should not be authenticated when session is partially initialized', function() { - return test_unauthorized({ first_factor: true }); - }); - }); -}); -