Skip to content

Commit

Permalink
test: First functional tests for Restify framework
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Middleware's second parameter is an object now, it contains definition of storage,
framework, etc.
  • Loading branch information
alexey-detr committed Dec 3, 2017
1 parent db84e18 commit 8d6c778
Show file tree
Hide file tree
Showing 8 changed files with 987 additions and 92 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,16 @@ Don't ever get confused when adding session middleware into your project!

It's all about **auzy**. The Node.js middleware library made to add sessions support.

**The current project state is unstable, please be careful using it.**
**The project is not production ready, please be careful using it.**

## Available storages

- Redis [alexey-detr/auzy-storage-redis](https://github.com/alexey-detr/auzy-storage-redis)

## Examples

For usage with specific framework see [examples directory](https://github.com/alexey-detr/auzy/tree/master/examples).

## Quick start

Install auzy and session storage (Redis in this example)
Expand All @@ -40,15 +44,14 @@ const auzyConfig = {
// Session name will appear as request and response header name below.
sessionName: 'X-Session-Token',

// TTL is specified in seconds and means the session expiration time.
// TTL is the session expiration time in seconds.
ttl: 60 * 60 * 24 * 30 * 6,

// Contains logic to fetch session ID from request object.
// String with session ID must be returned.
receiveSessionId: (req, sessionName) => req.header(sessionName),

// Contains logic to send session ID to client via response object.
// There is no any return value expected here, arrow function is just for shortness.
sendSessionId: (res, sessionName, sessionId) => res.header(sessionName, sessionId),

// Contains logic to fetch user data from database.
Expand Down
4 changes: 3 additions & 1 deletion SessionRequestHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ class SessionRequestHandler {
if (this.config.loadUser) {
this.req.user = await this.config.loadUser(this.sessionData);
}
this.sendSession();
if (!this.config.alwaysSend) {
this.sendSession();
}
return this.saveSession();
}

Expand Down
61 changes: 61 additions & 0 deletions __test__/functional/restify.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const got = require('got');

const url = 'http://localhost:9001';
let server;

beforeAll(() => {
return require('../../examples/restify').then((srv) => server = srv);
});

describe('Restify', () => {
it('should restrict /secret path', async () => {
const secretPromise = got.get(url + '/secret');
await expect(secretPromise).rejects.toHaveProperty('statusCode', 403);
});

it('should restrict /secret path with wrong token', async () => {
const secretPromise = got.get(url + '/secret', {
headers: {'X-Session-Token': 'my-fake-token'}
});
await expect(secretPromise).rejects.toHaveProperty('statusCode', 403);
});

it('should login and show data from /secret path', async () => {
// login
const loginResponse = await got.post(url + '/login', {
body: {name: 'Bob'},
json: true,
});
const token = loginResponse.headers['x-session-token'];

// secret
const secretPromise = got.get(url + '/secret', {
headers: {'X-Session-Token': token}
});
await expect(secretPromise).resolves.toHaveProperty('statusCode', 200);
});

it('should login, logout and restrict /secret path', async () => {
// login
const loginResponse = await got.post(url + '/login', {
body: {name: 'Bob'},
json: true,
});
const token = loginResponse.headers['x-session-token'];

// logout
await got.post(url + '/logout', {
headers: {'X-Session-Token': token}
});

// secret
const secretPromise = got.get(url + '/secret', {
headers: {'X-Session-Token': token}
});
await expect(secretPromise).rejects.toHaveProperty('statusCode', 403);
});
});

afterAll(() => {
server.close();
});
67 changes: 67 additions & 0 deletions examples/restify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const restify = require('restify');
const auzy = require('../index');

const users = [{
id: 1,
name: 'Bob',
email: 'bob@mail.com',
}];

const auzyConfig = {
session: {
sessionName: 'X-Session-Token',
// ttl: 60 * 60 * 24 * 30 * 6,
alwaysSend: true,
receiveSessionId: (req, sessionName) => req.header(sessionName),
sendSessionId: (res, sessionName, sessionId) => res.header(sessionName, sessionId),
loadUser: (sessionData) => {
const index = users.findIndex(user => user.id === sessionData.userId);
if (index === -1) {
return null;
}
return users[index];
},
},
};
const auzyEnvironment = {
framework: 'restify',
};

const server = restify.createServer();
server.use(restify.plugins.bodyParser());
server.use(auzy(auzyConfig, auzyEnvironment));

server.post('/login', async (req, res, next) => {
const index = users.findIndex(user => user.name === req.body.name);
if (index !== -1) {
const user = users[index];
await req.session.authenticate({userId: user.id});
res.send({name: req.user.name});
} else {
res.send(404, {error: 'User not found'});
}
next();
});

server.get('/secret', (req, res, next) => {
if (req.user) {
res.send({email: req.user.email});
} else {
res.send(403, {error: 'Restricted area'});
}
next();
});

server.post('/logout', (req, res, next) => {
req.session.destroy();
res.send(200);
next();
});

const launchPromise = new Promise((resolve) => {
server.listen(9001, () => {
resolve(server);
});
});

module.exports = launchPromise;
43 changes: 35 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,48 @@
'use strict';

module.exports = (config, storage = null) => {
const SessionRequestHandler = require('./SessionRequestHandler');

const defaultMiddleware = (storageObject, config) => {
return async (req, res, next) => {
req.session = new SessionRequestHandler(req, res, storageObject, config.session);
await req.session.loadSession();
next();
};
};

const koaMiddleware = (storageObject, config) => {
return async (ctx, next) => {
ctx.session = new SessionRequestHandler(ctx.request, ctx.response, storageObject, config.session);
await ctx.session.loadSession();
await next();
};
};

const frameworkMiddleware = {
default: defaultMiddleware,
restify: defaultMiddleware,
express: defaultMiddleware,
connect: defaultMiddleware,
koa: koaMiddleware,
};

module.exports = (config, {storage = null, framework = 'default'}) => {
let storageObject;
if (typeof storage === 'string' || storage instanceof String) {
const auzyStorage = require(`auzy-storage-${storage}`);
storageObject = new auzyStorage(config.storage);
} else {
// Object storage is non-persistent storage for sessions
// Object storage is a non-persistent storage for sessions
const ObjectStorage = require('./ObjectStorage');
const objectStorage = new ObjectStorage(config.storage);
storageObject = storage || objectStorage;
}
const SessionRequestHandler = require('./SessionRequestHandler');

return async (req, res, next) => {
req.session = new SessionRequestHandler(req, res, storageObject, config.session);
await req.session.loadSession();
next();
};
if (!frameworkMiddleware[framework]) {
const middleware = `Framework ${framework} is not supported, please specify one of ` +
`this: ${Object.keys(frameworkMiddleware).join(', ')}`;
throw new Error(middleware);
}

return frameworkMiddleware[framework](storageObject, config);
};
51 changes: 0 additions & 51 deletions package-lock.json

This file was deleted.

7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
"license": "MIT",
"repository": "https://github.com/alexey-detr/auzy",
"devDependencies": {
"connect": "^3.6.5",
"cz-conventional-changelog": "^2.1.0",
"jest": "^21.2.1"
"express": "^4.16.2",
"got": "^8.0.1",
"jest": "^21.2.1",
"koa": "^2.4.1",
"restify": "^6.3.4"
},
"config": {
"commitizen": {
Expand Down
Loading

0 comments on commit 8d6c778

Please sign in to comment.