Move Authentication validator and routes to typescript

pull/33/head
Clement Michaud 2017-05-21 12:27:12 +02:00
parent c98c07832d
commit fad23ff3be
7 changed files with 261 additions and 185 deletions

View File

@ -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);
}

41
src/lib/routes.ts 100644
View File

@ -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);
}

View File

@ -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<express.Request, string>(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();
});
};

View File

@ -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();
});
}

View File

@ -1,17 +1,51 @@
import sinon = require("sinon"); import sinon = require("sinon");
import express = require("express");
export interface RequestMock { export interface RequestMock {
app?: any; app?: any;
body?: any; body?: any;
session?: any; session?: any;
headers?: any; headers?: any;
get?: any;
} }
export interface ResponseMock { export interface ResponseMock {
send: sinon.SinonStub | sinon.SinonSpy; send: sinon.SinonStub | sinon.SinonSpy;
sendStatus: sinon.SinonStub;
sendFile: sinon.SinonStub;
sendfile: sinon.SinonStub;
status: sinon.SinonStub; status: sinon.SinonStub;
json: 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 { export function RequestMock(): RequestMock {
@ -25,6 +59,38 @@ export function ResponseMock(): ResponseMock {
return { return {
send: sinon.stub(), send: sinon.stub(),
status: 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()
}; };
} }

View File

@ -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 });
});
});
});

View File

@ -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 });
});
});
});