coco-the-bear-auth-sessiontoken
Version:
A route handler for CoCo The Bear that enforces session token authentication and authorization.
303 lines (250 loc) • 10.5 kB
JavaScript
const assertCalledOnce = require('@dannybster/coco-the-bear-test-utils').assertCalledOnce;
const EventEmitter = require('events');
const httpErrors = require('@dannybster/coco-the-bear-http-errors');
const rewire = require('rewire');
const should = require('should');
const uuid = require('uuid');
const systemUnderTest = rewire('../src/authentication');
describe('authentication.js', function () {
describe('#setUp', function () {
let sessionOptions;
let mockCookieParser;
let mockExpressSession;
let passport;
beforeEach(function createCommonPreConditionsForConfigureSessionMiddleware() {
function passportInitialiseMiddleware() {
}
function passportSessionMiddleware() {
}
function cookieParserMiddleware() {
}
mockCookieParser = () => {
return cookieParserMiddleware;
};
mockCookieParser.middleware = cookieParserMiddleware;
passport = {
initialize: () => passportInitialiseMiddleware,
session: () => passportSessionMiddleware,
deserializeUser: (fn) => {
passport.deserializeUser.parameters.fn = fn;
},
serializeUser: (fn) => {
passport.serializeUser.parameters.fn = fn;
},
};
passport.initialize.middleware = passportInitialiseMiddleware;
passport.session.middleware = passportSessionMiddleware;
passport.deserializeUser.parameters = {};
passport.serializeUser.parameters = {};
sessionOptions = {
secret: 'This is a story about an ordinary teddy bear...',
store: { name: 'I am a session store.' },
};
function expressSessionMiddleware() {
}
mockExpressSession = (options) => {
mockExpressSession.parameters = { options };
return expressSessionMiddleware;
};
mockExpressSession.middleware = expressSessionMiddleware;
systemUnderTest.__set__('cookieParser', mockCookieParser);
systemUnderTest.__set__('expressSession', mockExpressSession);
systemUnderTest.__set__('passport', passport);
});
it('it should configure the use express-session middleware with the supplied options.', function testConfigureExpressSessionMiddleware() {
const expectedSessionOptions = Object.assign({}, sessionOptions,
systemUnderTest.extraOptions);
systemUnderTest.setUp(sessionOptions);
mockExpressSession.parameters.options.should.eql(expectedSessionOptions);
});
it('it should tell the app to use the express-session middleware.', function testAddExpressSessionMiddleware() {
const middlewares = systemUnderTest.setUp(sessionOptions);
middlewares[0].should.eql(mockExpressSession.middleware);
});
it('it should tell the app to use an initialized passport middleware function.', function testAddPassportInitializeMiddleware() {
const middlewares = systemUnderTest.setUp(sessionOptions);
middlewares[1].should.eql(passport.initialize.middleware);
});
it('it should tell the app to use the passport session middleware function.', function testAddPassportInitializeMiddleware() {
const middlewares = systemUnderTest.setUp(sessionOptions);
middlewares[2].should.eql(passport.session.middleware);
});
it('it should tell the app to use the cookie-parser middleware.', function testAddExpressSessionMiddleware() {
const middlewares = systemUnderTest.setUp(sessionOptions);
middlewares[3].should.eql(mockCookieParser.middleware);
});
it('it should set the passport deserializeUser function.', function testAddPassportDeserialize() {
systemUnderTest.setUp(sessionOptions);
passport.deserializeUser.parameters.should.eql({ fn: systemUnderTest.deserializeUser });
});
it('it should set the passport serializeUser function.', function testAddPassportDeserialize() {
systemUnderTest.setUp(sessionOptions);
passport.serializeUser.parameters.should.eql({ fn: systemUnderTest.deserializeUser });
});
});
describe('User serialization and deserialixation for passport.js', function () {
const user = { _id: uuid.v4() };
describe('#serializeUser', function () {
it('it should call back with a null error object.', function testSerializeUser(done) {
systemUnderTest.serializeUser({}, assertCalledOnce((err) => {
should.not.exist(err);
done();
}));
});
it('it should call back with the user object.', function testSerializeUser(done) {
systemUnderTest.serializeUser(user, assertCalledOnce((err, result) => {
result.should.eql(user);
done();
}));
});
});
describe('#deserializeUser', function () {
it('it should call back with a null error object.', function testSerializeUser(done) {
systemUnderTest.deserializeUser({}, assertCalledOnce((err) => {
should.not.exist(err);
done();
}));
});
it('it should call back with the user object.', function testSerializeUser(done) {
systemUnderTest.deserializeUser(user, assertCalledOnce((err, result) => {
result.should.eql(user);
done();
}));
});
});
});
describe('#authenticateRequest', function () {
describe('If req.user exists', function () {
let req;
beforeEach(function createCommonPreConditionsForAuthenticatedOk() {
const emitter = new EventEmitter();
req = {
user: { _id: uuid.v4().toString() },
cookies: {
'connect.sid': 'session token',
},
events: {
emitter,
},
body: {
},
};
});
it('It should call next', function testAuthenticatedOk(done) {
systemUnderTest.authenticateRequest(req, null, assertCalledOnce((err) => {
should.not.exist(err);
done();
}));
});
it('It should emit an authenticated event.', function testAuthenticatedOkEmitsEvent(done) {
req.events.emitter.on('app-event', assertCalledOnce((event) => {
const expectedEvent = {
message: 'A session token was authenticated. See the object for more information',
object: {
user: req.user,
session: req.cookies['connect.sid'],
},
};
event.should.eql(expectedEvent);
done();
}));
systemUnderTest.authenticateRequest(req, null, () => { });
});
it('It should add a property req.body.auth.user with the value of the user object.', function testAddUserObject(done) {
systemUnderTest.authenticateRequest(req, null, assertCalledOnce(() => {
req.body.auth.should.eql({ user: req.user });
done();
}));
});
});
describe('If req.user does not exist', function () {
function createErrorEmitter() {
const emitter = new EventEmitter();
// Ignore errors so they don't break tests.
emitter.on('error', () => { });
return emitter;
}
describe('And a connect.sid cookie does not exist', function () {
let expectedError;
let req;
beforeEach(function createCommonPreConditionsForAuthenticatedErrorWithoutSession() {
const emitter = createErrorEmitter();
req = {
cookies: {
key: 'value',
},
events: {
emitter,
},
body: {
},
};
const body = { message: 'Session token not present at connect.sid' };
const info = { cookies: req.cookies };
const message = 'No session token provided.';
expectedError = httpErrors.unauthorized.createError(message, info, body);
});
it('It should call next with a descriptive unauthorized error', function testIsNotAuthenticated(done) {
systemUnderTest.authenticateRequest(req, null, assertCalledOnce((err) => {
err.should.eql(expectedError);
done();
}));
});
it('It should emit an unauthorized error event.', function testIsNotAuthenticatedWithoutSessionEmitsError(done) {
req.events.emitter.on('error', assertCalledOnce((event) => {
event.should.eql(expectedError);
done();
}));
systemUnderTest.authenticateRequest(req, null, () => { });
});
it('It should not add a property req.body.auth.user.', function testDoNotAddUserObject(done) {
systemUnderTest.authenticateRequest(req, null, assertCalledOnce(() => {
should.not.exist(req.body.auth);
done();
}));
});
});
describe('And a connect.sid cookie does exist', function () {
let expectedError;
let req;
beforeEach(function createCommonPreConditionsForAuthenticatedErrorWithoutSession() {
const emitter = createErrorEmitter();
req = {
cookies: {
key: 'value',
'connect.sid': 'I am the cookie monster!',
},
events: {
emitter,
},
body: {
},
};
const body = { message: 'Session token present at connect.sid is invalid' };
const info = { cookies: req.cookies };
const message = 'Invalid session token provided.';
expectedError = httpErrors.unauthorized.createError(message, info, body);
});
it('It should call next with a descriptive unauthorized error', function testIsNotAuthenticated(done) {
systemUnderTest.authenticateRequest(req, null, assertCalledOnce((err) => {
err.should.eql(expectedError);
done();
}));
});
it('It should emit an unauthorized error event.', function testIsNotAuthenticatedWithInvalidSessionEmitsError(done) {
req.events.emitter.on('error', assertCalledOnce((event) => {
event.should.eql(expectedError);
done();
}));
systemUnderTest.authenticateRequest(req, null, () => { });
});
it('It should not add a property req.body.auth.user.', function testDoNotAddUserObject(done) {
systemUnderTest.authenticateRequest(req, null, assertCalledOnce(() => {
should.not.exist(req.body.auth);
done();
}));
});
});
});
});
});