Skip to content

Commit

Permalink
significant refactoring of the SecurityProvider. Added factory class
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmarbles committed May 12, 2011
1 parent 1f60ea4 commit cd5d1c8
Show file tree
Hide file tree
Showing 4 changed files with 246 additions and 193 deletions.
2 changes: 1 addition & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = require('./lib/security_provider.js');
module.exports = require('./lib/security_provider_factory.js');
233 changes: 46 additions & 187 deletions lib/security_provider.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
exports.version = '0.0.1';

var sys = require('sys'),
events = require('events'),
fs = require('fs'),
Expand All @@ -11,55 +9,68 @@ var SecurityProvider = function(config) {

events.EventEmitter.call(this);

/*
* Configuration options;
* loginUrl: req.url path to use to implicitly emit the 'loginRequest' event
* logoutUrl: req.url path to use to implicitly emit the 'logout' event
* defaultKey: Key that will be automatically assigned to newly
* logged in users.
* hasKeyFn: function to be assigned to req.session.security.hasKey
*/
var config = (config) ? config : {};

this.loginUrl = (config.loginUrl) ? config.loginUrl : '/login';
this.logoutUrl = (config.logoutUrl) ? config.logoutUrl : '/logout';
this.defaultKey = (config.defaultKey) ? config.defaultKey : 'LOGGED_IN_USER';

var loginMatcher = new RegExp(this.loginUrl);
var logoutMatcher = new RegExp(this.logoutUrl);
var logPrefix = 'Komainu';
var matchers = {};
var definitions = {};
var defaultAuthenticator;
var loginURL;
var logoutURL;
var ignoredElements = [];
var testCredentials = [];
var loginErrorsTpl = '<ul class="errors"><li>{error}</li></ul>';
var self = this;

this.define = function(eventName, matchFunction) {
matchers[eventName] = matchFunction;
sys.log(logPrefix + ': implicitEventDefined (' + eventName + ')');
self.emit('implicitEventDefined', eventName, matchFunction);
this.getDefinitions = function() {
return definitions;
};

this.secure = function(authenticator) {
this.setLoginURL = function(url) {
loginURL = url;
};

/*
* If no security algorithm was provided, then supply a default
* one. We'll simply check for the existence of the default key.
*/
var authenticator = (typeof authenticator == 'function') ? authenticator : function(req, res) {
this.getLoginURL = function() {
return loginURL;
};

if (!req.session || !req.session.komainu || !req.session.komainu.keys)
return false;
this.setLogoutURL = function(url) {
logoutURL = url;
};

var keys = req.session.komainu.keys;
this.getLogoutURL = function() {
return logoutURL;
};

for (var i = 0; i < keys.length; i++) {
if (keys[i] == self.defaultKey)
return true;
}
this.setDefaultAuthenticator = function(authenticator) {
defaultAuthenticator = authenticator;
};

return false;
};
this.getDefaultAuthenticator = function() {
return defaultAuthenticator;
};

this.define = function(eventName, matchFunction) {
definitions[eventName] = matchFunction;
sys.log('Komainu: implicitEventDefined (' + eventName + ')');
self.emit('implicitEventDefined', eventName, matchFunction);
};

this.secure = function(authenticator) {

var authenticator = (authenticator) ? authenticator : self.getDefaultAuthenticator();

var secureFn = function(req, res, next) {
self.finalize = next;
if (self.ignore(req, res)) {
sys.log(logPrefix + ': Ignoring ' + req.url);
sys.log('Komainu: Ignoring ' + req.url);
return next();
} else {
sys.log(logPrefix + ': match (' + req.method + ' ' + req.url+ ')');
sys.log('Komainu: match (' + req.method + ' ' + req.url+ ')');
self.emit('match', req, res, authenticator);
}
};
Expand All @@ -80,6 +91,7 @@ var SecurityProvider = function(config) {
ignoredElements.push(fn);
};

// TODO: change to a hash {username: {...}} to O(1) access from 0(n)
this.addCredentials = function(username, password, keys) {
testCredentials.push({
username: username,
Expand All @@ -88,167 +100,14 @@ var SecurityProvider = function(config) {
});
};

var hasCredentials = function(username, password) {
this.hasCredentials = function(username, password) {
for (var i = 0; i < testCredentials.length; i++) {
if (testCredentials[i].username == username && testCredentials[i].password == password)
return true;
}
return false;
};

this.addIgnore(function(req, res) {
if (req.method == 'GET' && /favicon.ico|.css/.test(req.url))
return true;
return false;
});

this.define('loginShow', function(req, res) {
return (req.method == 'GET' && loginMatcher.test(req.url));
});

this.define('loginRequest', function(req, res) {
return (req.method == 'POST' && loginMatcher.test(req.url));
});

this.define('authenticate', function(req, res) {
return !loginMatcher.test(req.url) && !logoutMatcher.test(req.url);
});

this.define('logout', function(req, res) {
return (req.method == 'GET' && logoutMatcher.test(req.url));
});

var match = function(req, res, authenticator) {
for (var key in matchers) {
var fn = matchers[key];
if (fn(req, res)) {
sys.log(logPrefix + ': ' + key);
self.emit(key, req, res, authenticator);
}
}
};
match.komainu = true;
this.on('match', match);

var loginShow = function(req, res, err) {

if (!err)
err = "";

var errTpl = loginErrorsTpl.replace('{error}', err);
var loginPage = staticLogin.toString().replace('{loginUrl}', self.loginUrl);
var loginPage = loginPage.replace('{errors}', errTpl);

res.writeHead(200, {'Content-Type':'text/html'});
res.end(loginPage);

};
loginShow.komainu = true;
this.on('loginShow', loginShow);

var loginRequest = function(req, res) {
req.on('data', function(data) {
var credentials = querystring.parse(data.toString());
sys.log(logPrefix + ': login (' + credentials.username + ')');
self.emit('login', req, res, credentials.username, credentials.password, [self.defaultKey]);
});
};
loginRequest.komainu = true;
this.on('loginRequest', loginRequest);

var login = function(req, res, username, password, keys) {
if (hasCredentials(username, password)) {
sys.log(logPrefix + ': initSession (' + username + ')');
self.emit('initSession', req, res, username, keys);
sys.log(logPrefix + ': loginSuccess (' + username + ')');
self.emit('loginSuccess', req, res, username);
} else {
sys.log(logPrefix + ': loginFailure (' + username + ')');
self.emit('loginFailure', req, res, username);
}
};
login.komainu = true;
this.on('login', login);

var loginSuccess = function(req, res, username) {
res.writeHead(302, {'Location':'/'});
res.end();
};
loginSuccess.komainu = true;
this.on('loginSuccess', loginSuccess);

var loginFailure = function(req, res, username) {
sys.log(logPrefix + ': loginShow');
self.emit('loginShow', req, res, ['Invalid Credentials']);
};
loginFailure.komainu = true;
this.on('loginFailure', loginFailure);

var logout = function(req, res, username) {

delete req.session.komainu;

sys.log(logPrefix + ': sessionEnded');
self.emit('sessionEnded', req, res);

sys.log(logPrefix + ': logoutSuccess');
self.emit('logoutSuccess', req, res);
};
logout.komainu = true;
this.on('logout', logout);

var initSession = function(req, res, username, keys) {
req.session.komainu = {
keys: keys
}
sys.log(logPrefix + ': sessionStarted (' + username + ' ' + keys + ')');
self.emit('sessionStarted', req, res, username, keys);
};
initSession.komainu = true;
this.on('initSession', initSession);

var logoutSuccess = function(req, res) {
res.writeHead(302, {'Location':self.loginUrl});
res.end();
};
logoutSuccess.komainu = true;
this.on('logoutSuccess', logoutSuccess);

var authenticate = function(req, res, authenticator) {
if (!authenticator(req, res)) {
sys.log(logPrefix + ': accessDenied');
self.emit('accessDenied', req, res);
} else {
sys.log(logPrefix + ': accessGranted');
self.emit('accessGranted', req, res);
}
};
authenticate.komainu = true;
this.on('authenticate', authenticate);

var accessDenied = function(req, res) {
sys.log(logPrefix + ': loginShow');
self.emit('loginShow', req, res, ['Access Denied']);
};
accessDenied.komainu = true;
this.on('accessDenied', accessDenied);

var accessGranted = function(req, res) {
self.finalize();
};
accessGranted.komainu = true;
this.on('accessGranted', accessGranted);

this.on('newListener', function(event, listener) {
var listeners = self.listeners(event);
for (var i = 0; i < listeners.length; i++) {
if (listeners[i].komainu) {
sys.log(logPrefix + ': Overriding ' + event);
self.removeListener(event, listeners[i]);
}
}
});

};

sys.inherits(SecurityProvider, events.EventEmitter);
Expand Down
Loading

0 comments on commit cd5d1c8

Please sign in to comment.