From 9b306d45be8b69a3e872691367d8281e46b4a208 Mon Sep 17 00:00:00 2001 From: ciaranj Date: Sun, 21 Aug 2011 19:24:40 +0100 Subject: [PATCH] Fairly sizeable refactor (whilst maintaining existing interface) Separate out the code more logically and reduce function createion per request --- lib/authExecutionScope.js | 6 +- lib/auth_middleware.js | 99 ++++++++++++++++++++ lib/index.js | 192 +------------------------------------- lib/requestMethods.js | 129 +++++++++++++++++++++++++ lib/strategyExecutor.js | 12 +-- 5 files changed, 240 insertions(+), 198 deletions(-) create mode 100644 lib/auth_middleware.js create mode 100644 lib/requestMethods.js diff --git a/lib/authExecutionScope.js b/lib/authExecutionScope.js index 266e9d3..86c0e32 100644 --- a/lib/authExecutionScope.js +++ b/lib/authExecutionScope.js @@ -2,12 +2,10 @@ * Copyright(c) 2010 Ciaran Jessup * MIT Licensed */ -var AuthExecutionScope= module.exports = function( scope, trace, request, response ) { +var AuthExecutionScope= module.exports = function( scope, trace ) { this.executionResult= { authenticated: undefined }; this.scope= scope; this._trace= trace; - this.request= request; - this.response= response; } /** @@ -19,7 +17,7 @@ AuthExecutionScope.prototype.trace= function( message ) { if( this.executionResult.currentStrategy ) { messagePrefix= this.executionResult.currentStrategy + ": "; } - this._trace( messagePrefix + message, this.request, this.response, this.scope, "***" ) + this._trace( messagePrefix + message, this.scope, "***" ) } AuthExecutionScope.prototype.fail= function(callback) { diff --git a/lib/auth_middleware.js b/lib/auth_middleware.js new file mode 100644 index 0000000..f720c96 --- /dev/null +++ b/lib/auth_middleware.js @@ -0,0 +1,99 @@ +/*! + * Copyright(c) 2010 Ciaran Jessup + * MIT Licensed + */ + var connect= require('connect') + , RequestMethods= require('./requestMethods') + , StrategyExecutor= require('./strategyExecutor') + , Tracing= require('./tracing'); + +/** + * Construct the authentication middleware. + * Construction can take 2 forms: + * auth(()|[()]) - A single configured strategy, or array of strategies. + * auth({ strategies:()|[()...] + * [trace: true|false|function(message, req, [scope])}]) - More powerful variant that allows for passing in other configuration options, none yet defined. + */ +module.exports = function(optionsOrStrategy) { + + var i, strategies, strategyExecutor, options, traceFunction, server; + + if( !optionsOrStrategy ) throw new Error("You must specify at least one strategy to use the authentication middleware, even if it is anonymous."); + // Constructor form 1 + if( Array.isArray(optionsOrStrategy) || ( optionsOrStrategy.authenticate !== undefined && + optionsOrStrategy.strategies === undefined ) ) { + strategies= Array.isArray(optionsOrStrategy) ? optionsOrStrategy : [optionsOrStrategy]; + options= {trace: false}; + } + else { + options= optionsOrStrategy + strategies= Array.isArray(optionsOrStrategy.strategies) ? optionsOrStrategy.strategies : [optionsOrStrategy.strategies]; + } + + if( !options.trace ) { // If options.trace is specified as false or undefined we no-op the messages. + traceFunction= Tracing.nullTrace; + } + else if( options.trace === true ) { // If options.trace is really true then we log out to console + traceFunction= Tracing.standardTrace; + } + else { // Custom provided trace function + traceFunction= options.trace; + } + + // Construct the strategy executor. + strategyExecutor= new StrategyExecutor( strategies ) + + // Construct the middleware that adapts the request object to provide authentication primitives. + server= connect.createServer( + function auth(req, res, next) { + + // Mix-in the static utility methods (the methods are directly on the request, and don't need the response object). + req.getAuthDetails= RequestMethods.getAuthDetails; + req.isAuthenticated= RequestMethods.isAuthenticated; + req.isUnAuthenticated= RequestMethods.isUnAuthenticated; + req.logout= RequestMethods.logout; + + // If there is a session middleware, use it. + if( req.session && req.session.auth ) { + req._connect_auth= req.session.auth; + } + else { + // Create the auth holder if needed. + if( ! req.getAuthDetails() ) { + createAuthDetails(req); + } + } + + // These methods require the request & response to be in their closures. + req.authenticate= function(strategy, opts, callback) { + RequestMethods.authenticate.call( this, strategy, opts, callback, strategyExecutor, res ); + } + + // Assign a tracer so if needed routes can trace. + req.getAuthDetails().trace= function( message, scope, linePrefix ) { + traceFunction( message, req, res, scope, linePrefix ); + }; + + // Now we've added our requisite methods to the request, call the next part of the middleware chain + // (which may in fact be a middleware piece that enforces authentication!) + next(); + }); + + // Some strategies require routes to be defined, so give them a chance to do so. + for(i=0;i< strategies.length; i++ ) { + if( strategies[i].setupRoutes ) { + strategies[i].setupRoutes(server); + } + } + + return server; +}; + +// Utility functions +function createAuthDetails( request ) { + var auth= { scopedUsers: {} }; + request._connect_auth= auth; + if( request.session ) { + request.session.auth= auth; + } + }; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index dcb90b7..3c56e8b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -6,195 +6,10 @@ /** * Module dependencies. */ -var connect= require('connect') - , fs= require('fs') - , StrategyExecutor= require('./strategyExecutor') - , Tracing= require('./tracing'); +var Auth= require('./auth_middleware') + , fs= require('fs'); -/** - * Construct the authentication middleware. - * Construction can take 2 forms: - * auth(()|[()]) - A single configured strategy, or array of strategies. - * auth({ strategies:()|[()...] - * [trace: true|false|function(message, req, [scope])}]) - More powerful variant that allows for passing in other configuration options, none yet defined. - */ -var Auth = module.exports = function(optionsOrStrategy) { - - var strategies; - var options; - var trace; - if( !optionsOrStrategy ) throw new Error("You must specify at least one strategy to use the authentication middleware, even if it is anonymous."); - // Constructor form 1 - if( Array.isArray(optionsOrStrategy) || ( optionsOrStrategy.authenticate !== undefined && - optionsOrStrategy.strategies === undefined ) ) { - strategies= Array.isArray(optionsOrStrategy) ? optionsOrStrategy : [optionsOrStrategy]; - options= {trace: false}; - } - else { - options= optionsOrStrategy - strategies= Array.isArray(optionsOrStrategy.strategies) ? optionsOrStrategy.strategies : [optionsOrStrategy.strategies]; - } - - if( !options.trace ) { // If options.trace is specified as false or undefined we no-op the messages. - trace= Tracing.nullTrace; - } - else if( options.trace === true ) { // If options.trace is really true then we log out to console - trace= Tracing.standardTrace; - } - else { // Custom provided trace function - trace= options.trace; - } - - // Otherwise we use the provided trace function. - - var isAuthenticated = function( request, scope ) { - if( scope === undefined ) { - return (request.getAuthDetails().user) ? true : false; - } - else { - return (request.getAuthDetails().scopedUsers[scope] && request.getAuthDetails().scopedUsers[scope].user) ? true : false; - } - }; - - function createAuthDetails(req) { - var auth= { scopedUsers: {} }; - req._connect_auth= auth; - if( req.session ) { - req.session.auth= auth; - } - }; - - - var server= connect.createServer( - function auth(req, res, next) { - // Setup the utility methods - req.authenticate = function(strategy, opts, callback) { - var strategy, opts, callback; - var scope; - - //ughhh pull this rubbish somewhere tidy... - if( strategy && opts && callback ) { - var type= typeof strategy; - if( strategy.constructor != Array ) { - strategy= [strategy] - } - scope= opts.scope; - } - else if( strategy && opts ) { - callback= opts; - var type= typeof strategy; - if( strategy.constructor == Array ) { - // do nothing - } - else if( type == 'string' ) { - strategy= [strategy]; - } - else if( type == 'object') { - scope= strategy.scope - strategy= undefined; - } - } - else if( strategy ) { - callback= strategy; - strategy= undefined; - } - - // Choose the first strategy defined if no strategy provided - if( !strategy && strategies.length >0 ) { - strategy= [strategies[0].name]; - } - - trace( "Authenticating ("+req.headers.host + req.url+")", req, res, scope, ">>>" ); - if( isAuthenticated(req, scope) ) { - trace( "Authentication successful (Already Authenticated)", req, res, scope, "<<<" ); - callback(null, true); - } - else { - strategyExecutor.authenticate(strategy, scope, trace, req, res, function (error, executionResult) { - //TODO: This needs tidying up, the HTTP strategies have bled... - if( executionResult) { - req.getAuthDetails().errorResponse= executionResult.errorResponse; - } - - if(error) { - trace( "Authentication error: "+ error, req, res, scope, "<<<" ); - callback(error); - } - else { - if( executionResult.authenticated === true ) { - trace( "Authentication successful", req, res, scope, "<<<" ); - if( scope === undefined) { - req.getAuthDetails().user= executionResult.user; - } - else { - if( req.getAuthDetails().scopedUsers[scope] === undefined ) { - req.getAuthDetails().scopedUsers[scope] = {}; - } - req.getAuthDetails().scopedUsers[scope].user= executionResult.user; - } - } - else if( executionResult.authenticated === false ) { - trace( "Authentication failed", req, res, scope, "<<<" ); - } - else { - trace( "Authentication ongoing (Requires browser interaction)", req, res, scope, "<<<" ); - } - callback(null, executionResult.authenticated) - } - }); - } - }; - req.getAuthDetails= function() { - return req._connect_auth - }; - req.isAuthenticated= function(scope) { - return isAuthenticated( req, scope ); - }; - req.isUnAuthenticated= function(scope) { - return !isAuthenticated( req, scope ); - }; - req.logout= function(scope, callback) { - // Optional scope, optional callback - if( scope !== undefined && typeof scope == "function" ) { - callback= scope; - scope= undefined; - } - trace( "Logout", req, res, scope, "!!!" ); - - if( scope === undefined) { - delete req.getAuthDetails().user; - req.getAuthDetails().scopedUsers= {}; - } - else { - delete req.getAuthDetails().scopedUsers[scope].user; - } - if( callback ) callback(); - } - - // If there is a session middleware, use it. - if( req.session && req.session.auth ) { - req._connect_auth= req.session.auth; - } - else { - // Create the auth holder if needed. - if( ! req.getAuthDetails() ) { - createAuthDetails(req); - } - } - next(); - }); - - // Some strategies require routes to be defined, so give them a chance to do so. - for(var i=0;i< strategies.length; i++ ) { - if( strategies[i].setupRoutes ) { - strategies[i].setupRoutes(server); - } - } - - var strategyExecutor = new StrategyExecutor( strategies ) - - return server; -}; +module.exports= Auth; /** * Auto-load bundled strategies with getters. @@ -213,6 +28,7 @@ function augmentAuthWithStrategy(filename, path) { enumerable:true}); } } + //TODO: Meh could make this recurse neatly over directories, but I'm lazy. fs.readdirSync(__dirname + '/auth.strategies').forEach(function(filename){ augmentAuthWithStrategy(filename, '/auth.strategies') diff --git a/lib/requestMethods.js b/lib/requestMethods.js new file mode 100644 index 0000000..f22fbba --- /dev/null +++ b/lib/requestMethods.js @@ -0,0 +1,129 @@ +/*! + * Copyright(c) 2010 Ciaran Jessup + * MIT Licensed + */ + +/* + * This file contains the methods that will become 'mixed-in' with the connect request object, namely: + * + * authenticate( [strategy|options], callback(err, succcessFailOngoing) ) + * getAuthDetails + * isAuthenticated( [scope] ) + * isUnAuthenticated( [scope] ) + * logout( [scope], [callback(err)]) + */ +module.exports.authenticate= function(strategy, opts, callback, strategyExecutor, res) { + var strategy, opts, callback; + var scope; + + var trace= this.getAuthDetails().trace; + var req= this; + + //ughhh pull this rubbish somewhere tidy... + if( strategy && opts && callback ) { + var type= typeof strategy; + if( strategy.constructor != Array ) { + strategy= [strategy] + } + scope= opts.scope; + } + else if( strategy && opts ) { + callback= opts; + var type= typeof strategy; + if( strategy.constructor == Array ) { + // do nothing + } + else if( type == 'string' ) { + strategy= [strategy]; + } + else if( type == 'object') { + scope= strategy.scope + strategy= undefined; + } + } + else if( strategy ) { + callback= strategy; + strategy= undefined; + } + + // Choose the first strategy defined if no strategy provided + if( !strategy && strategyExecutor.strategies.length >0 ) { + strategy= [strategyExecutor.strategies[0].name]; + } + + trace( "Authenticating ("+this.headers.host + this.url+")", scope, ">>>" ); + if( req.isAuthenticated(scope) ) { + trace( "Authentication successful (Already Authenticated)", scope, "<<<" ); + callback(null, true); + } + else { + strategyExecutor.authenticate(strategy, scope, trace, req, res, function (error, executionResult) { + //TODO: This needs tidying up, the HTTP strategies have bled... + if( executionResult) { + req.getAuthDetails().errorResponse= executionResult.errorResponse; + } + + if(error) { + trace( "Authentication error: "+ error, scope, "<<<" ); + callback(error); + } + else { + if( executionResult.authenticated === true ) { + trace( "Authentication successful", scope, "<<<" ); + if( scope === undefined) { + req.getAuthDetails().user= executionResult.user; + } + else { + if( req.getAuthDetails().scopedUsers[scope] === undefined ) { + req.getAuthDetails().scopedUsers[scope] = {}; + } + req.getAuthDetails().scopedUsers[scope].user= executionResult.user; + } + } + else if( executionResult.authenticated === false ) { + trace( "Authentication failed", scope, "<<<" ); + } + else { + trace( "Authentication ongoing (Requires browser interaction)", scope, "<<<" ); + } + callback(null, executionResult.authenticated) + } + }); + } + }; + + // mixins... +module.exports.getAuthDetails= function() { + return this._connect_auth +}; + +module.exports.isAuthenticated= function(scope) { + if( scope === undefined ) { + return (this.getAuthDetails().user) ? true : false; + } + else { + return (this.getAuthDetails().scopedUsers[scope] && this.getAuthDetails().scopedUsers[scope].user) ? true : false; + } + }; + +module.exports.isUnAuthenticated= function(scope) { + return !this.isAuthenticated( scope ); + }; + +module.exports.logout= function(scope, callback) { + // Optional scope, optional callback + if( scope !== undefined && typeof scope == "function" ) { + callback= scope; + scope= undefined; + } + this.getAuthDetails().trace( "Logout", scope, "!!!" ); + + if( scope === undefined) { + delete this.getAuthDetails().user; + this.getAuthDetails().scopedUsers= {}; + } + else { + delete this.getAuthDetails().scopedUsers[scope].user; + } + if( callback ) callback(); + }; \ No newline at end of file diff --git a/lib/strategyExecutor.js b/lib/strategyExecutor.js index bb1e9bd..e353a4d 100644 --- a/lib/strategyExecutor.js +++ b/lib/strategyExecutor.js @@ -13,9 +13,9 @@ module.exports= function(strategies) { }; module.exports.prototype.authenticate= function(strategies, scope, trace, request, response, callback) { - var executionScope= new AuthExecutionScope( scope, trace, request, response ); + var executionScope= new AuthExecutionScope( scope, trace ); if( !this.strategies || this.strategies.length ==0 ) { - trace( "Unable to find a strategy to authenticate with", request, response, scope, "###"); + trace( "Unable to find a strategy to authenticate with", scope, "###"); callback( null, executionScope.executionResult ); } else { @@ -32,12 +32,12 @@ module.exports.prototype.authenticate= function(strategies, scope, trace, reques carryOn= true; } catch(e) { - trace( "Error choosing strategy: "+ e, request, response, scope, "###"); + trace( "Error choosing strategy: "+ e, scope, "###"); callback(e); } if( carryOn ) { if( strategiesToTest.length == 0 ) { - trace( "Tested all strategies :" + util.inspect(executionScope.executionResult), request, response, scope, "###"); + trace( "Tested all strategies :" + util.inspect(executionScope.executionResult), scope, "###"); callback( null, executionScope.executionResult ); } else { @@ -49,13 +49,13 @@ module.exports.prototype.authenticate= function(strategies, scope, trace, reques //todo: scope! ;(function next(e) { if (executionScope.executionResult.halted || e || complete === total) { - trace( "Tested all strategies", request, response, scope, "###"); + trace( "Tested all strategies", scope, "###"); callback(e, executionScope.executionResult) } else { strategy= strategiesToTest[complete++]; if( strategy.isValid === undefined || strategy.isValid() ) { - trace( "Attempting authentication with: " + strategy.name, request, response, scope, "###" ); + trace( "Attempting authentication with: " + strategy.name, scope, "###" ); executionScope.executionResult.currentStrategy= strategy.name; strategy.authenticate.call(executionScope, request, response, next); }