Skip to content

Commit

Permalink
First revision
Browse files Browse the repository at this point in the history
  • Loading branch information
asafdav committed May 17, 2014
0 parents commit 7d8908a
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 0 deletions.
Empty file added .covignore
Empty file.
5 changes: 5 additions & 0 deletions .gitignore
@@ -0,0 +1,5 @@
/node_modules/
npm-debug.log
.idea
lib-cov
coverage.html
10 changes: 10 additions & 0 deletions Makefile
@@ -0,0 +1,10 @@
test:
@node node_modules/mocha/bin/mocha

test-cov: lib-cov
@AUTH_EXTRA_COV=1 node_modules/mocha/bin/mocha -R html-cov > coverage.html

lib-cov:
@node node_modules/jscoverage/bin/jscoverage lib lib-cov

.PHONY: test test-cov
10 changes: 10 additions & 0 deletions README.md
@@ -0,0 +1,10 @@
hapi-auth-extra
===============

Additional authentication toolbox for HapiJS.
It includes:
* ACL support
* Authentication strategy for API's (Token based)

How to use it:
--------------
1 change: 1 addition & 0 deletions index.js
@@ -0,0 +1 @@
module.exports = require('./lib');
76 changes: 76 additions & 0 deletions lib/acl.js
@@ -0,0 +1,76 @@
/**
* Checks if the user has the wanted roles
*
* @param request
* @param reply
*/
exports.checkRoles = function(request, reply) {
var user = request.auth.credentials;
var requiredRole = request.pre.role;
if (!exports.isGranted(user.role, requiredRole)) reply(Hapi.error.unauthorized('Unauthorized'));

reply('Has grants');
//reply({'bad': 'very'}).takeover().code(500); -- Take over example
};

exports.isGranted = function(userRole, requiredRole) {
var userRoles = RoleHierarchy[userRole];
return (userRoles.indexOf(requiredRole) > -1);
};

/**
* Returns a function that returns the required role for an handler
* @param ROLE
* @returns {Function}
*/
exports.rolePrerequsites = function(ROLE) {
return function(reqeust, reply) {
reply(ROLE);
};
};

/**
* Uses the provided query to fetch the wanted entity.
*
* @param query - function(id, cb) that returns the entity to the callback.
* @param param - The route parameter to use in order to fetch the entity (useually id)
*
* @returns {Function}
*/
exports.fetchACLEntity = function(query, param) {
return function(request, reply) {
var entityId = request.params[param];
query(entityId, function(err, entity) {
if (err) return reply(Hapi.error.internal('Bad request', err));
if (!entity) return reply(Hapi.error.notFound());
reply(entity);
});
};
};

/**
* Verifies that the user has permission to access the wanted entity.
* This pre function is depended on fetchACLEntity and must run after it as it needs the entity the verify
* if the user has access to it.
*
* @param role - The wanted role, undefined means any role
* @returns {Function}
*/
exports.validateACL = function(role) {
return function(request, reply) {
if (!request.pre.entity) throw Error('validateACL must run after fetchACLEntity');
if (!request.auth.credentials) throw Error('User is required, please make sure this method requires authentication');
var user = request.auth.credentials, entity = request.pre.entity;

if(typeof entity.validateACL !== 'function') throw Error(client.constructor.name + ' doesn\'t have validateACL method');
entity.validateACL(user, role, function(err, isValid) {
if (err) throw Error(err);

// Not granted
if (!isValid) reply(Hapi.error.unauthorized('Unauthorized', err));

// Valid
reply(isValid);
});
};
};
68 changes: 68 additions & 0 deletions lib/auth.js
@@ -0,0 +1,68 @@
/**
* Created by asafdav on 3/11/14.
*/
var Hoek = require('hoek')

var Hapi = require('hapi');
var RoleHierarchy = require('./../model/document/user').RoleHierarchy;
var UserCollection = require('../model/collection/user');


/**
* Implement the passport strategy
* @param username
* @param password
* @param done
*/
exports.passportLocalStrategy = function(username, password, done) {
UserCollection.login({email: username, password: password}, function(err, user) {
if (err) return done(null, false, { 'message': 'An error occurred' });
if (!user) return done(null, false, {'message': 'Invalid credentials'});

return done(null, user);
});
};

/**
* Implements bearer authenticator
* @param reply
* @returns {Function}
*/
exports.bearerAuthenticator = function(reply) {
return function(err, user, info) {
if (err) return reply(Hapi.error.unauthorized(err));
if (!user) return reply(Hapi.error.unauthorized('No user was found for this token'));
return reply(null, { credentials: user });
}
};

/**
* Implements facebook authenticator
* @param accessToken
* @param refreshToken
* @param profile
* @param done
* @returns {*}
*/
exports.facebookAuthenticator = function(accessToken, refreshToken, profile, done) {
return done(null, false, {message: 'TBD'});
};

/**
* Find the user by token. If there is no user with the given token, set
* the user to `false` to indicate failure. Otherwise, return the
* authenticated `user`. Note that in a production-ready application, one
* would want to validate the token for authenticity.
*
* @param token
* @param done
* @constructor
*/
exports.BearerStrategy = function(token, done) {
UserCollection.findUserByToken(token, function(err, user) {
if (err) { return done(err); }
if (!user) { return done(null, false); }

return done(null, user);
});
};
24 changes: 24 additions & 0 deletions lib/index.js
@@ -0,0 +1,24 @@
var Roles = require('./roles');

var internals = {
defaults: {
roles: Roles
}
};


exports.register = function (plugin, options, next) {
var settings = Hoek.applyToDefaults(internals.defaults, options || {});

plugin.bind({
config: settings
});

plugin.ext('onPostAuth', internals.onPostAuth);

next();
};

internals.onPostHandler = function(request, next) {
next();
};
20 changes: 20 additions & 0 deletions lib/roles.js
@@ -0,0 +1,20 @@
// TODO - generalize

var RoleTypes = {
SUPER_ADMIN: 'SUPER_ADMIN',
ADMIN: 'ADMIN',
USER: 'USER',
GUEST: 'GUEST'
};


var RoleHierarchy = {};
RoleHierarchy[RoleTypes.SUPER_ADMIN] = [RoleTypes.SUPER_ADMIN, RoleTypes.ADMIN, RoleTypes.USER, RoleTypes.GUEST];
RoleHierarchy[RoleTypes.ADMIN] = [RoleTypes.ADMIN, RoleTypes.USER, RoleTypes.GUEST];
RoleHierarchy[RoleTypes.USER] = [RoleTypes.USER, RoleTypes.GUEST];
RoleHierarchy[RoleTypes.GUEST] = [RoleTypes.GUEST];

module.exports = {
roles: RoleTypes,
hierarchy: RoleHierarchy
};
37 changes: 37 additions & 0 deletions package.json
@@ -0,0 +1,37 @@
{
"name": "hapi-auth-extra",
"version": "0.0.0",
"description": "Additional auth toolbox for HapiJS including ACL support",
"main": "index.js",
"scripts": {
"test": "make test-cov"
},
"repository": {
"type": "git",
"url": "git@github.com:asafdav/hapi-auth-extra.git"
},
"keywords": [
"hapi",
"auth",
"acl"
],
"author": {
"name": "Asaf David",
"email": "asafdav@gmail.com",
"url": "http://about.me/asafdavid"
},
"license": "ISC",
"bugs": {
"url": "https://github.com/asafdav/hapi-auth-extra/issues"
},
"homepage": "https://github.com/asafdav/hapi-auth-extra",
"dependencies": {
"hapi": "^4.1.4",
"hoek": "^2.1.0"
},
"devDependencies": {
"chai": "^1.9.1",
"jscoverage": "^0.5.0-rc2",
"mocha": "^1.18.2"
}
}
34 changes: 34 additions & 0 deletions test/index.js
@@ -0,0 +1,34 @@
/**
* Created by asafdav on 17/05/14.
*/

// External modules
var Chai = require('chai');
var Hapi = require('hapi');

// Internal modules
var libpath = process.env['AUTH_EXTRA_COV'] ? '../lib-cov' : '../lib';
var Plugin = require(libpath + '/index');


// Declare internals
var internals = {};

// Test shortcuts
var expect = Chai.expect;

describe('Hapi-Auth-Extra', function() {

describe('onPostAuth', function() {
it('dummy', function(done) {
var server = new Hapi.Server();
server.route({ method: 'GET', path: '/', handler: function (request, reply) { reply("TEST"); } });

server.inject('/', function(res) {
expect(1).to.equal(1);
done();
});
})
});

});

0 comments on commit 7d8908a

Please sign in to comment.