Skip to content

Commit

Permalink
Authentication test.
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexey Melnikov committed Jul 15, 2016
1 parent f1d3cd0 commit 72e1ded
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
[![Coverage Status](https://coveralls.io/repos/github/alexey-ernest/hapi-throttling/badge.svg)](https://coveralls.io/github/alexey-ernest/hapi-throttling)
[![npm version](https://img.shields.io/npm/v/hapi-throttling.svg?style=flat)](https://www.npmjs.com/package/hapi-throttling)

HAPI rate limit plugin with Redis storage.
HAPI rate limit plugin with Redis storage for Node 0.10.25.
6 changes: 3 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@
/**
* Plugin registration point.
*/
exports.register = function (server, options, next) {
exports.register = function (plugin, options, next) {
var settings = Hoek.applyToDefaults(DEFAULTS, options);
var redisClient = redis.createClient({host: options.redis.host, port: options.redis.port});

// Before auth
server.ext('onPreAuth', function(request, reply) {
plugin.ext('onPostAuth', function(request, reply) {
// get request key
settings.getKey(request, reply, function (err, key) {
if (err) {
Expand Down Expand Up @@ -92,7 +92,7 @@
});

// After handler
server.ext('onPostHandler', function(request, reply) {
plugin.ext('onPostHandler', function(request, reply) {
var response;
if (request.plugins[PLUGIN_NAME]) {
response = request.response;
Expand Down
117 changes: 117 additions & 0 deletions test/auth.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
var Hapi = require('hapi');
var async = require('async');
var chai = require('chai');
chai.should();

describe('HAPI with auth', function () {
var server;
var max = 5;
var duration = 10000;
var user;

beforeEach(function() {
user = {
name: 'user',
password: 'password',
id: new Date().getTime()
};

var users = {};
users[user.name] = user;

server = new Hapi.Server();
server.connection({port: 8000});

var options = {
redis: {
host: process.env.REDIS_HOST,
port: 6379
},
getKey: function (request, reply, done) {
done(null, request.auth.credentials.id);
},
getLimit: function (request, reply, done) {
done(null, {
max: max,
duration: duration
});
}
};

server.register([{
register: require('../'),
options: options
}, {
register: require('./basic-auth')
}], function (err) {
if (err) {
throw err;
}

// register simple strategy
server.auth.strategy('simple', 'basic', {
validateFunc: function (username, password, callback) {
var u = users[username];
if (!u) {
return callback(null, false);
}
callback(null, true, { id: u.id, name: u.name });
}
});
});

server.route({
method: 'GET',
path: '/admin',
config: {
auth: 'simple',
handler: function (request, reply) {
reply('OK');
},
}
});
});

it('should responds 401 with X-RateLimit-* headers for unauthenticated user', function (done) {
var options = {
method: 'GET',
url: '/admin'
};

server.inject(options, function (response) {
response.statusCode.should.equal(401);
done();
});
});

it('should responds 200 with X-RateLimit-* headers for authenticated user', function (done) {
function basicHeader (username, password) {
return 'Basic ' + (new Buffer(username + ':' + password, 'utf8')).toString('base64');
}

var options = {
method: 'GET',
url: '/admin',
headers: {
authorization: basicHeader(user.name, user.password)
}
};

server.inject(options, function (response) {
response.statusCode.should.equal(200);

response.headers.should.have.property('x-ratelimit-limit');
response.headers['x-ratelimit-limit'].should.equal(max);

response.headers.should.have.property('x-ratelimit-remaining');
response.headers['x-ratelimit-remaining'].should.equal(max - 1);

response.headers.should.have.property('x-ratelimit-reset');
var reset = +response.headers['x-ratelimit-reset'];
(reset - (Date.now() + duration)/1000 <= 0).should.be.true;

done();
});
});

});
78 changes: 78 additions & 0 deletions test/basic-auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Load modules

var Boom = require('boom');
var Hoek = require('hoek');


// Declare internals

var internals = {};


exports.register = function (plugin, options, next) {
plugin.auth.scheme('basic', internals.implementation);
next();
};


internals.implementation = function (server, options) {

Hoek.assert(options, 'Missing basic auth strategy options');
Hoek.assert(typeof options.validateFunc === 'function', 'options.validateFunc must be a valid function in basic scheme');

var settings = Hoek.clone(options);

var scheme = {
authenticate: function (request, reply) {
var req = request.raw.req;
var authorization = req.headers.authorization;
if (!authorization) {
return reply(Boom.unauthorized(null, 'Basic'));
}

var parts = authorization.split(/\s+/);

if (parts[0].toLowerCase() !== 'basic') {
return reply(Boom.unauthorized(null, 'Basic'));
}

if (parts.length !== 2) {
return reply(Boom.badRequest('Bad HTTP authentication header format', 'Basic'));
}

var credentialsParts = new Buffer(parts[1], 'base64').toString().split(':');
if (credentialsParts.length !== 2) {
return reply(Boom.badRequest('Bad header internal syntax', 'Basic'));
}

var username = credentialsParts[0];
var password = credentialsParts[1];

if (!username && !settings.allowEmptyUsername) {
return reply(Boom.unauthorized('HTTP authentication header missing username', 'Basic'));
}

settings.validateFunc(username, password, function (err, isValid, credentials) {
credentials = credentials || null;
if (err) {
return reply(err, { credentials: credentials, log: { tags: ['auth', 'basic'], data: err } });
}
if (!isValid) {
return reply(Boom.unauthorized('Bad username or password', 'Basic'), { credentials: credentials });
}
if (!credentials || typeof credentials !== 'object') {
return reply(Boom.badImplementation('Bad credentials object received for Basic auth validation'), { log: { tags: 'credentials' } });
}

// Authenticated
return reply.continue({credentials: credentials});
});
}
};

return scheme;
};

exports.register.attributes = {
name: 'hapi-basic-auth'
};
File renamed without changes.
File renamed without changes.

0 comments on commit 72e1ded

Please sign in to comment.