From 02337add0de1ecfd23a4ad379a0994eafc9e17a3 Mon Sep 17 00:00:00 2001 From: divlo Date: Mon, 28 Dec 2020 23:51:03 +0100 Subject: [PATCH] test: add authorizer and fixture --- package.json | 9 ++- src/__test__/authorizer.test.ts | 139 ++++++++++++++++++++++++++++++++ src/__test__/fixture/index.ts | 57 +++++++++++++ 3 files changed, 202 insertions(+), 3 deletions(-) create mode 100644 src/__test__/authorizer.test.ts create mode 100644 src/__test__/fixture/index.ts diff --git a/package.json b/package.json index 4decf81..af1f101 100644 --- a/package.json +++ b/package.json @@ -57,9 +57,7 @@ "jest": { "preset": "ts-jest", "testEnvironment": "node", - "rootDir": "./src", - "collectCoverage": true, - "coverageDirectory": "../coverage/" + "rootDir": "./src" }, "ts-standard": { "files": [ @@ -89,15 +87,20 @@ "@commitlint/cli": "11.0.0", "@commitlint/config-conventional": "11.0.0", "@release-it/conventional-changelog": "2.0.0", + "@types/express": "4.17.9", "@types/jest": "26.0.19", "@types/jsonwebtoken": "8.5.0", "@types/node": "14.14.16", + "@types/server-destroy": "1.0.1", "@types/socket.io": "2.1.12", + "@types/socket.io-client": "1.4.34", + "axios": "0.21.1", "express": "4.17.1", "husky": "4.3.6", "jest": "26.6.3", "release-it": "14.2.2", "rimraf": "3.0.2", + "server-destroy": "1.0.1", "snazzy": "9.0.0", "socket.io": "2.3.0", "socket.io-client": "2.3.0", diff --git a/src/__test__/authorizer.test.ts b/src/__test__/authorizer.test.ts new file mode 100644 index 0000000..0b43320 --- /dev/null +++ b/src/__test__/authorizer.test.ts @@ -0,0 +1,139 @@ +import io from 'socket.io-client' +import { fixtureStart, fixtureStop } from './fixture/index' +import axios from 'axios' + +describe('authorizer', () => { + describe('when the user is not logged in', () => { + beforeEach((done) => { + jest.setTimeout(15_000) + fixtureStart(done) + }) + + afterEach((done) => { + fixtureStop(done) + }) + + it('should emit error with unauthorized handshake', (done) => { + const socket = io.connect('http://localhost:9000?token=boooooo') + socket.on('error', (err: any) => { + expect(err.message).toEqual('jwt malformed') + expect(err.code).toEqual('invalid_token') + socket.close() + done() + }) + }) + }) + + describe('when the user is logged in', () => { + describe('authorizer disallows query string token when specified in startup options', () => { + let token: string = '' + + beforeEach((done) => { + jest.setTimeout(15_000) + fixtureStart( + async () => { + const response = await axios.post('http://localhost:9000/login') + token = response.data.token + done() + }, + { auth_header_required: true } + ) + }) + + afterEach((done) => { + fixtureStop(done) + }) + + test('auth headers are supported', (done) => { + const socket = io.connect('http://localhost:9000', { + // @ts-ignore + extraHeaders: { Authorization: `Bearer ${token}` } + }) + socket.on('connect', () => { + socket.close() + done() + }) + }) + + test('auth token in query string is disallowed', (done) => { + const socket = io.connect('http://localhost:9000', { + query: `token=${token}` + }) + socket.on('error', (err: any) => { + expect(err.message).toEqual('Server requires Authorization Header') + expect(err.code).toEqual('missing_authorization_header') + socket.close() + done() + }) + }) + }) + + describe('authorizer all auth types allowed', () => { + let token: string = '' + + beforeEach((done) => { + jest.setTimeout(15_000) + fixtureStart(async () => { + const response = await axios.post('http://localhost:9000/login') + token = response.data.token + done() + }) + }) + + afterEach((done) => { + fixtureStop(done) + }) + + it('auth headers are supported', (done) => { + const socket = io.connect('http://localhost:9000', { + // @ts-ignore + extraHeaders: { Authorization: `Bearer ${token}` } + }) + socket.on('connect', () => { + socket.close() + done() + }) + }) + + it('should do the handshake and connect', (done) => { + const socket = io.connect('http://localhost:9000', { + query: `token=${token}` + }) + socket.on('connect', () => { + socket.close() + done() + }) + }) + }) + }) + + describe('unsigned token', () => { + let token = + 'eyJhbGciOiJub25lIiwiY3R5IjoiSldUIn0.eyJuYW1lIjoiSm9obiBGb28ifQ.' + + beforeEach((done) => { + jest.setTimeout(15_000) + fixtureStart(done) + }) + + afterEach((done) => { + fixtureStop(done) + }) + + it('should not do the handshake and connect', (done) => { + const socket = io.connect('http://localhost:9000', { + query: `token=${token}` + }) + socket + .on('connect', () => { + socket.close() + done(new Error("this shouldn't happen")) + }) + .on('error', (err: any) => { + socket.close() + expect(err.message).toEqual('jwt signature is required') + done() + }) + }) + }) +}) diff --git a/src/__test__/fixture/index.ts b/src/__test__/fixture/index.ts new file mode 100644 index 0000000..e4d6337 --- /dev/null +++ b/src/__test__/fixture/index.ts @@ -0,0 +1,57 @@ +import express from 'express' +import jwt from 'jsonwebtoken' +import { Server as HttpServer } from 'http' +import { Server as HttpsServer } from 'https' +import socketIo, { Server as SocketIoServer } from 'socket.io' +import enableDestroy from 'server-destroy' + +import { authorize } from '../../index' + +interface Socket { + io: null | SocketIoServer + init: (httpServer: HttpServer | HttpsServer) => void +} + +const socket: Socket = { + io: null, + init(httpServer) { + socket.io = socketIo(httpServer) + } +} + +let server: HttpServer | null = null + +export const fixtureStart = (done: any, optionsAuth: any = {}) => { + const options = Object.assign( + { + secret: 'aaafoo super sercret', + timeout: 1000, + handshake: true + }, + optionsAuth + ) + const app = express() + app.use(express.json()) + app.post('/login', (_req, res) => { + const profile = { + first_name: 'John', + last_name: 'Doe', + email: 'john@doe.com', + id: 123 + } + const token = jwt.sign(profile, options.secret, { expiresIn: 60 * 60 * 5 }) + return res.json({ token }) + }) + server = app.listen(9000, done) + socket.init(server) + socket.io?.use(authorize(options)) + enableDestroy(server) +} + +export const fixtureStop = (callback: Function) => { + socket.io?.close() + try { + server?.destroy() + } catch (err) {} + callback() +}