UNPKG

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